jiti-meet/libs/strophe/strophe.jingle.adapter.js

515 lines
19 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 = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
/**
* Array of ssrcs that will be added on next modifySources call.
* @type {Array}
*/
this.addssrc = [];
/**
* Array of ssrcs that will be added on next modifySources call.
* @type {Array}
*/
this.removessrc = [];
/**
* Pending operation that will be done during modifySources call.
* Currently 'mute'/'unmute' operations are supported.
*
* @type {String}
*/
this.pendingop = null;
/**
* Flag indicates that peer connection stream have changed and modifySources should proceed.
* @type {boolean}
*/
this.switchstreams = false;
// 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', self.signalingState);
if (self.onsignalingstatechange !== null) {
self.onsignalingstatechange(event);
}
};
this.oniceconnectionstatechange = null;
this.peerconnection.oniceconnectionstatechange = function (event) {
self.trace('oniceconnectionstatechange', self.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.maxstats) {
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() {
var publicLocalDescription = simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription);
return publicLocalDescription;
});
TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() {
var publicRemoteDescription = simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription);
return publicRemoteDescription;
});
}
TraceablePeerConnection.prototype.addStream = function (stream) {
this.trace('addStream', stream.id);
simulcast.resetSender();
try
{
this.peerconnection.addStream(stream);
}
catch (e)
{
console.error(e);
return;
}
};
TraceablePeerConnection.prototype.removeStream = function (stream, stopStreams) {
this.trace('removeStream', stream.id);
simulcast.resetSender();
if(stopStreams) {
stream.getAudioTracks().forEach(function (track) {
track.stop();
});
stream.getVideoTracks().forEach(function (track) {
track.stop();
});
}
this.peerconnection.removeStream(stream);
};
TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
this.trace('createDataChannel', label, opts);
return this.peerconnection.createDataChannel(label, opts);
};
TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
var self = this;
description = simulcast.transformLocalDescription(description);
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;
description = simulcast.transformRemoteDescription(description);
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.hardMuteVideo = function (muted) {
this.pendingop = muted ? 'mute' : 'unmute';
};
TraceablePeerConnection.prototype.enqueueAddSsrc = function(channel, ssrcLines) {
if (!this.addssrc[channel]) {
this.addssrc[channel] = '';
}
this.addssrc[channel] += ssrcLines;
};
TraceablePeerConnection.prototype.addSource = function (elem) {
console.log('addssrc', new Date().getTime());
console.log('ice', this.iceConnectionState);
var sdp = new SDP(this.remoteDescription.sdp);
var mySdp = new SDP(this.peerconnection.localDescription.sdp);
var self = this;
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
var semantics = this.getAttribute('semantics');
var ssrcs = $(this).find('>source').map(function () {
return this.getAttribute('ssrc');
}).get();
if (ssrcs.length != 0) {
lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
}
});
tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
if(mySdp.containsSSRC(ssrc)){
/**
* This happens when multiple participants change their streams at the same time and
* ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
* addssrc are scheduled for update IQ. See
*/
console.warn("Got add stream request for my own ssrc: "+ssrc);
return;
}
$(this).find('>parameter').each(function () {
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
lines += ':' + $(this).attr('value');
lines += '\r\n';
});
});
sdp.media.forEach(function(media, idx) {
if (!SDPUtil.find_line(media, 'a=mid:' + name))
return;
sdp.media[idx] += lines;
self.enqueueAddSsrc(idx, lines);
});
sdp.raw = sdp.session + sdp.media.join('');
});
};
TraceablePeerConnection.prototype.enqueueRemoveSsrc = function(channel, ssrcLines) {
if (!this.removessrc[channel]){
this.removessrc[channel] = '';
}
this.removessrc[channel] += ssrcLines;
};
TraceablePeerConnection.prototype.removeSource = function (elem) {
console.log('removessrc', new Date().getTime());
console.log('ice', this.iceConnectionState);
var sdp = new SDP(this.remoteDescription.sdp);
var mySdp = new SDP(this.peerconnection.localDescription.sdp);
var self = this;
$(elem).each(function (idx, content) {
var name = $(content).attr('name');
var lines = '';
tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
var semantics = this.getAttribute('semantics');
var ssrcs = $(this).find('>source').map(function () {
return this.getAttribute('ssrc');
}).get();
if (ssrcs.length != 0) {
lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
}
});
tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
tmp.each(function () {
var ssrc = $(this).attr('ssrc');
// This should never happen, but can be useful for bug detection
if(mySdp.containsSSRC(ssrc)){
console.error("Got remove stream request for my own ssrc: "+ssrc);
return;
}
$(this).find('>parameter').each(function () {
lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
if ($(this).attr('value') && $(this).attr('value').length)
lines += ':' + $(this).attr('value');
lines += '\r\n';
});
});
sdp.media.forEach(function(media, idx) {
if (!SDPUtil.find_line(media, 'a=mid:' + name))
return;
sdp.media[idx] += lines;
self.enqueueRemoveSsrc(idx, lines);
});
sdp.raw = sdp.session + sdp.media.join('');
});
};
TraceablePeerConnection.prototype.modifySources = function(successCallback) {
var self = this;
if (this.signalingState == 'closed') return;
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
// There is nothing to do since scheduled job might have been executed by another succeeding call
if(successCallback){
successCallback();
}
return;
}
// FIXME: this is a big hack
// https://code.google.com/p/webrtc/issues/detail?id=2688
// ^ has been fixed.
if (!(this.signalingState == 'stable' && this.iceConnectionState == 'connected')) {
console.warn('modifySources not yet', this.signalingState, this.iceConnectionState);
this.wait = true;
window.setTimeout(function() { self.modifySources(successCallback); }, 250);
return;
}
if (this.wait) {
window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
this.wait = false;
return;
}
// Reset switch streams flag
this.switchstreams = false;
var sdp = new SDP(this.remoteDescription.sdp);
// add sources
this.addssrc.forEach(function(lines, idx) {
sdp.media[idx] += lines;
});
this.addssrc = [];
// remove sources
this.removessrc.forEach(function(lines, idx) {
lines = lines.split('\r\n');
lines.pop(); // remove empty last element;
lines.forEach(function(line) {
sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
});
});
this.removessrc = [];
// FIXME:
// this was a hack for the situation when only one peer exists
// in the conference.
// check if still required and remove
if (sdp.media[0])
sdp.media[0] = sdp.media[0].replace('a=recvonly', 'a=sendrecv');
if (sdp.media[1])
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
sdp.raw = sdp.session + sdp.media.join('');
this.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
function() {
if(self.signalingState == 'closed') {
console.error("createAnswer attempt on closed state");
return;
}
self.createAnswer(
function(modifiedAnswer) {
// change video direction, see https://github.com/jitsi/jitmeet/issues/41
if (self.pendingop !== null) {
var sdp = new SDP(modifiedAnswer.sdp);
if (sdp.media.length > 1) {
switch(self.pendingop) {
case 'mute':
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
break;
case 'unmute':
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
break;
}
sdp.raw = sdp.session + sdp.media.join('');
modifiedAnswer.sdp = sdp.raw;
}
self.pendingop = null;
}
// FIXME: pushing down an answer while ice connection state
// is still checking is bad...
//console.log(self.peerconnection.iceConnectionState);
// trying to work around another chrome bug
//modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
self.setLocalDescription(modifiedAnswer,
function() {
//console.log('modified setLocalDescription ok');
if(successCallback){
successCallback();
}
},
function(error) {
console.error('modified setLocalDescription failed', error);
}
);
},
function(error) {
console.error('modified answer failed', error);
}
);
},
function(error) {
console.error('modify failed', error);
}
);
};
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) {
answer = simulcast.transformAnswer(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...
if(!errback)
errback = function () {
}
this.peerconnection.getStats(null,callback,errback);
} else {
this.peerconnection.getStats(callback);
}
};