381 lines
14 KiB
JavaScript
381 lines
14 KiB
JavaScript
function TraceablePeerConnection(ice_config, constraints) {
|
|
var self = this;
|
|
var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
|
|
this.peerconnection = new RTCPeerconnection(ice_config, constraints);
|
|
this.updateLog = [];
|
|
this.stats = {};
|
|
this.statsinterval = null;
|
|
this.maxstats = 300; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
|
|
|
|
// override as desired
|
|
this.trace = function(what, info) {
|
|
//console.warn('WTRACE', what, info);
|
|
self.updateLog.push({
|
|
time: new Date(),
|
|
type: what,
|
|
value: info || ""
|
|
});
|
|
};
|
|
this.onicecandidate = null;
|
|
this.peerconnection.onicecandidate = function (event) {
|
|
self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
|
|
if (self.onicecandidate !== null) {
|
|
self.onicecandidate(event);
|
|
}
|
|
};
|
|
this.onaddstream = null;
|
|
this.peerconnection.onaddstream = function (event) {
|
|
self.trace('onaddstream', event.stream.id);
|
|
if (self.onaddstream !== null) {
|
|
self.onaddstream(event);
|
|
}
|
|
};
|
|
this.onremovestream = null;
|
|
this.peerconnection.onremovestream = function (event) {
|
|
self.trace('onremovestream', event.stream.id);
|
|
if (self.onremovestream !== null) {
|
|
self.onremovestream(event);
|
|
}
|
|
};
|
|
this.onsignalingstatechange = null;
|
|
this.peerconnection.onsignalingstatechange = function (event) {
|
|
self.trace('onsignalingstatechange', event.srcElement.signalingState);
|
|
if (self.onsignalingstatechange !== null) {
|
|
self.onsignalingstatechange(event);
|
|
}
|
|
};
|
|
this.oniceconnectionstatechange = null;
|
|
this.peerconnection.oniceconnectionstatechange = function (event) {
|
|
self.trace('oniceconnectionstatechange', event.srcElement.iceConnectionState);
|
|
if (self.oniceconnectionstatechange !== null) {
|
|
self.oniceconnectionstatechange(event);
|
|
}
|
|
};
|
|
this.onnegotiationneeded = null;
|
|
this.peerconnection.onnegotiationneeded = function (event) {
|
|
self.trace('onnegotiationneeded');
|
|
if (self.onnegotiationneeded !== null) {
|
|
self.onnegotiationneeded(event);
|
|
}
|
|
};
|
|
self.ondatachannel = null;
|
|
this.peerconnection.ondatachannel = function (event) {
|
|
self.trace('ondatachannel', event);
|
|
if (self.ondatachannel !== null) {
|
|
self.ondatachannel(event);
|
|
}
|
|
}
|
|
if (!navigator.mozGetUserMedia) {
|
|
this.statsinterval = window.setInterval(function() {
|
|
self.peerconnection.getStats(function(stats) {
|
|
var results = stats.result();
|
|
for (var i = 0; i < results.length; ++i) {
|
|
//console.log(results[i].type, results[i].id, results[i].names())
|
|
var now = new Date();
|
|
results[i].names().forEach(function (name) {
|
|
var id = results[i].id + '-' + name;
|
|
if (!self.stats[id]) {
|
|
self.stats[id] = {
|
|
startTime: now,
|
|
endTime: now,
|
|
values: [],
|
|
times: []
|
|
};
|
|
}
|
|
self.stats[id].values.push(results[i].stat(name));
|
|
self.stats[id].times.push(now.getTime());
|
|
if (self.stats[id].values.length > self.maxstats) {
|
|
self.stats[id].values.shift();
|
|
self.stats[id].times.shift();
|
|
}
|
|
self.stats[id].endTime = now;
|
|
});
|
|
}
|
|
});
|
|
|
|
}, 1000);
|
|
}
|
|
};
|
|
|
|
dumpSDP = function(description) {
|
|
return 'type: ' + description.type + '\r\n' + description.sdp;
|
|
}
|
|
|
|
if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
|
|
TraceablePeerConnection.prototype.__defineGetter__('signalingState', function() { return this.peerconnection.signalingState; });
|
|
TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; });
|
|
TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() { return this.peerconnection.localDescription; });
|
|
TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() { return this.peerconnection.remoteDescription; });
|
|
}
|
|
|
|
TraceablePeerConnection.prototype.addStream = function (stream) {
|
|
this.trace('addStream', stream.id);
|
|
this.peerconnection.addStream(stream);
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.removeStream = function (stream) {
|
|
this.trace('removeStream', stream.id);
|
|
this.peerconnection.removeStream(stream);
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
|
|
this.trace('createDataChannel', label, opts);
|
|
this.peerconnection.createDataChannel(label, opts);
|
|
}
|
|
|
|
TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
|
|
var self = this;
|
|
this.trace('setLocalDescription', dumpSDP(description));
|
|
this.peerconnection.setLocalDescription(description,
|
|
function () {
|
|
self.trace('setLocalDescriptionOnSuccess');
|
|
successCallback();
|
|
},
|
|
function (err) {
|
|
self.trace('setLocalDescriptionOnFailure', err);
|
|
failureCallback(err);
|
|
}
|
|
);
|
|
/*
|
|
if (this.statsinterval === null && this.maxstats > 0) {
|
|
// start gathering stats
|
|
}
|
|
*/
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
|
|
var self = this;
|
|
this.trace('setRemoteDescription', dumpSDP(description));
|
|
this.peerconnection.setRemoteDescription(description,
|
|
function () {
|
|
self.trace('setRemoteDescriptionOnSuccess');
|
|
successCallback();
|
|
},
|
|
function (err) {
|
|
self.trace('setRemoteDescriptionOnFailure', err);
|
|
failureCallback(err);
|
|
}
|
|
);
|
|
/*
|
|
if (this.statsinterval === null && this.maxstats > 0) {
|
|
// start gathering stats
|
|
}
|
|
*/
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.close = function () {
|
|
this.trace('stop');
|
|
if (this.statsinterval !== null) {
|
|
window.clearInterval(this.statsinterval);
|
|
this.statsinterval = null;
|
|
}
|
|
this.peerconnection.close();
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
|
|
var self = this;
|
|
this.trace('createOffer', JSON.stringify(constraints, null, ' '));
|
|
this.peerconnection.createOffer(
|
|
function (offer) {
|
|
self.trace('createOfferOnSuccess', dumpSDP(offer));
|
|
successCallback(offer);
|
|
},
|
|
function(err) {
|
|
self.trace('createOfferOnFailure', err);
|
|
failureCallback(err);
|
|
},
|
|
constraints
|
|
);
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
|
|
var self = this;
|
|
this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
|
|
this.peerconnection.createAnswer(
|
|
function (answer) {
|
|
self.trace('createAnswerOnSuccess', dumpSDP(answer));
|
|
successCallback(answer);
|
|
},
|
|
function(err) {
|
|
self.trace('createAnswerOnFailure', err);
|
|
failureCallback(err);
|
|
},
|
|
constraints
|
|
);
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
|
|
var self = this;
|
|
this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
|
|
this.peerconnection.addIceCandidate(candidate);
|
|
/* maybe later
|
|
this.peerconnection.addIceCandidate(candidate,
|
|
function () {
|
|
self.trace('addIceCandidateOnSuccess');
|
|
successCallback();
|
|
},
|
|
function (err) {
|
|
self.trace('addIceCandidateOnFailure', err);
|
|
failureCallback(err);
|
|
}
|
|
);
|
|
*/
|
|
};
|
|
|
|
TraceablePeerConnection.prototype.getStats = function(callback, errback) {
|
|
if (navigator.mozGetUserMedia) {
|
|
// ignore for now...
|
|
} else {
|
|
this.peerconnection.getStats(callback);
|
|
}
|
|
};
|
|
|
|
// mozilla chrome compat layer -- very similar to adapter.js
|
|
function setupRTC() {
|
|
var RTC = null;
|
|
if (navigator.mozGetUserMedia) {
|
|
console.log('This appears to be Firefox');
|
|
var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
|
|
if (version >= 22) {
|
|
RTC = {
|
|
peerconnection: mozRTCPeerConnection,
|
|
browser: 'firefox',
|
|
getUserMedia: navigator.mozGetUserMedia.bind(navigator),
|
|
attachMediaStream: function (element, stream) {
|
|
element[0].mozSrcObject = stream;
|
|
element[0].play();
|
|
},
|
|
pc_constraints: {}
|
|
};
|
|
if (!MediaStream.prototype.getVideoTracks)
|
|
MediaStream.prototype.getVideoTracks = function () { return []; };
|
|
if (!MediaStream.prototype.getAudioTracks)
|
|
MediaStream.prototype.getAudioTracks = function () { return []; };
|
|
RTCSessionDescription = mozRTCSessionDescription;
|
|
RTCIceCandidate = mozRTCIceCandidate;
|
|
}
|
|
} else if (navigator.webkitGetUserMedia) {
|
|
console.log('This appears to be Chrome');
|
|
RTC = {
|
|
peerconnection: webkitRTCPeerConnection,
|
|
browser: 'chrome',
|
|
getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
|
|
attachMediaStream: function (element, stream) {
|
|
element.attr('src', webkitURL.createObjectURL(stream));
|
|
},
|
|
// DTLS should now be enabled by default but..
|
|
pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}
|
|
};
|
|
if (navigator.userAgent.indexOf('Android') != -1) {
|
|
RTC.pc_constraints = {}; // disable DTLS on Android
|
|
}
|
|
if (!webkitMediaStream.prototype.getVideoTracks) {
|
|
webkitMediaStream.prototype.getVideoTracks = function () {
|
|
return this.videoTracks;
|
|
};
|
|
}
|
|
if (!webkitMediaStream.prototype.getAudioTracks) {
|
|
webkitMediaStream.prototype.getAudioTracks = function () {
|
|
return this.audioTracks;
|
|
};
|
|
}
|
|
}
|
|
if (RTC === null) {
|
|
try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
|
|
}
|
|
return RTC;
|
|
}
|
|
|
|
function getUserMediaWithConstraints(um, resolution, bandwidth, fps) {
|
|
var constraints = {audio: false, video: false};
|
|
|
|
if (um.indexOf('video') >= 0) {
|
|
constraints.video = {mandatory: {}};// same behaviour as true
|
|
}
|
|
if (um.indexOf('audio') >= 0) {
|
|
constraints.audio = {};// same behaviour as true
|
|
}
|
|
if (um.indexOf('screen') >= 0) {
|
|
constraints.video = {
|
|
"mandatory": {
|
|
"chromeMediaSource": "screen"
|
|
}
|
|
};
|
|
}
|
|
|
|
if (resolution && !constraints.video) {
|
|
constraints.video = {mandatory: {}};// same behaviour as true
|
|
}
|
|
// see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
|
|
switch (resolution) {
|
|
// 16:9 first
|
|
case '1080':
|
|
case 'fullhd':
|
|
constraints.video.mandatory.minWidth = 1920;
|
|
constraints.video.mandatory.minHeight = 1080;
|
|
constraints.video.mandatory.minAspectRatio = 1.77;
|
|
break;
|
|
case '720':
|
|
case 'hd':
|
|
constraints.video.mandatory.minWidth = 1280;
|
|
constraints.video.mandatory.minHeight = 720;
|
|
constraints.video.mandatory.minAspectRatio = 1.77;
|
|
break;
|
|
case '360':
|
|
constraints.video.mandatory.minWidth = 640;
|
|
constraints.video.mandatory.minHeight = 360;
|
|
constraints.video.mandatory.minAspectRatio = 1.77;
|
|
break;
|
|
case '180':
|
|
constraints.video.mandatory.minWidth = 320;
|
|
constraints.video.mandatory.minHeight = 180;
|
|
constraints.video.mandatory.minAspectRatio = 1.77;
|
|
break;
|
|
// 4:3
|
|
case '960':
|
|
constraints.video.mandatory.minWidth = 960;
|
|
constraints.video.mandatory.minHeight = 720;
|
|
break;
|
|
case '640':
|
|
case 'vga':
|
|
constraints.video.mandatory.minWidth = 640;
|
|
constraints.video.mandatory.minHeight = 480;
|
|
break;
|
|
case '320':
|
|
constraints.video.mandatory.minWidth = 320;
|
|
constraints.video.mandatory.minHeight = 240;
|
|
break;
|
|
default:
|
|
if (navigator.userAgent.indexOf('Android') != -1) {
|
|
constraints.video.mandatory.minWidth = 320;
|
|
constraints.video.mandatory.minHeight = 240;
|
|
constraints.video.mandatory.maxFrameRate = 15;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (bandwidth) { // doesn't work currently, see webrtc issue 1846
|
|
if (!constraints.video) constraints.video = {mandatory: {}};//same behaviour as true
|
|
constraints.video.optional = [{bandwidth: bandwidth}];
|
|
}
|
|
if (fps) { // for some cameras it might be necessary to request 30fps
|
|
// so they choose 30fps mjpg over 10fps yuy2
|
|
if (!constraints.video) constraints.video = {mandatory: {}};// same behaviour as tru;
|
|
constraints.video.mandatory.minFrameRate = fps;
|
|
}
|
|
|
|
try {
|
|
RTC.getUserMedia(constraints,
|
|
function (stream) {
|
|
console.log('onUserMediaSuccess');
|
|
$(document).trigger('mediaready.jingle', [stream]);
|
|
},
|
|
function (error) {
|
|
console.warn('Failed to get access to local media. Error ', error);
|
|
$(document).trigger('mediafailure.jingle');
|
|
});
|
|
} catch (e) {
|
|
console.error('GUM failed: ', e);
|
|
$(document).trigger('mediafailure.jingle');
|
|
}
|
|
} |