Creates initial version of xmpp module.
This commit is contained in:
parent
ee94eca733
commit
e4e66a03d7
721
app.js
721
app.js
|
@ -1,17 +1,8 @@
|
||||||
/* jshint -W117 */
|
/* jshint -W117 */
|
||||||
/* application specific logic */
|
/* application specific logic */
|
||||||
var connection = null;
|
|
||||||
var authenticatedUser = false;
|
|
||||||
/* Initial "authentication required" dialog */
|
|
||||||
var authDialog = null;
|
|
||||||
/* Loop retry ID that wits for other user to create the room */
|
|
||||||
var authRetryId = null;
|
|
||||||
var activecall = null;
|
|
||||||
var nickname = null;
|
var nickname = null;
|
||||||
var focusMucJid = null;
|
var focusMucJid = null;
|
||||||
var roomName = null;
|
|
||||||
var ssrc2jid = {};
|
var ssrc2jid = {};
|
||||||
var bridgeIsDown = false;
|
|
||||||
//TODO: this array must be removed when firefox implement multistream support
|
//TODO: this array must be removed when firefox implement multistream support
|
||||||
var notReceivedSSRCs = [];
|
var notReceivedSSRCs = [];
|
||||||
|
|
||||||
|
@ -27,674 +18,12 @@ var ssrc2videoType = {};
|
||||||
* @type {String}
|
* @type {String}
|
||||||
*/
|
*/
|
||||||
var focusedVideoInfo = null;
|
var focusedVideoInfo = null;
|
||||||
var mutedAudios = {};
|
|
||||||
/**
|
|
||||||
* Remembers if we were muted by the focus.
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
var forceMuted = false;
|
|
||||||
/**
|
|
||||||
* Indicates if we have muted our audio before the conference has started.
|
|
||||||
* @type {boolean}
|
|
||||||
*/
|
|
||||||
var preMuted = false;
|
|
||||||
|
|
||||||
var localVideoSrc = null;
|
|
||||||
var flipXLocalVideo = true;
|
|
||||||
var isFullScreen = false;
|
|
||||||
var currentVideoWidth = null;
|
|
||||||
var currentVideoHeight = null;
|
|
||||||
|
|
||||||
var sessionTerminated = false;
|
|
||||||
|
|
||||||
function init() {
|
function init() {
|
||||||
|
|
||||||
|
|
||||||
RTC.addStreamListener(maybeDoJoin, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
|
||||||
RTC.start();
|
RTC.start();
|
||||||
|
xmpp.start(UI.getCreadentials);
|
||||||
|
|
||||||
var jid = document.getElementById('jid').value || config.hosts.anonymousdomain || config.hosts.domain || window.location.hostname;
|
|
||||||
connect(jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function connect(jid, password) {
|
|
||||||
connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind');
|
|
||||||
|
|
||||||
var settings = UI.getSettings();
|
|
||||||
var email = settings.email;
|
|
||||||
var displayName = settings.displayName;
|
|
||||||
if(email) {
|
|
||||||
connection.emuc.addEmailToPresence(email);
|
|
||||||
} else {
|
|
||||||
connection.emuc.addUserIdToPresence(settings.uid);
|
|
||||||
}
|
|
||||||
if(displayName) {
|
|
||||||
connection.emuc.addDisplayNameToPresence(displayName);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connection.disco) {
|
|
||||||
// for chrome, add multistream cap
|
|
||||||
}
|
|
||||||
connection.jingle.pc_constraints = RTC.getPCConstraints();
|
|
||||||
if (config.useIPv6) {
|
|
||||||
// https://code.google.com/p/webrtc/issues/detail?id=2828
|
|
||||||
if (!connection.jingle.pc_constraints.optional) connection.jingle.pc_constraints.optional = [];
|
|
||||||
connection.jingle.pc_constraints.optional.push({googIPv6: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!password)
|
|
||||||
password = document.getElementById('password').value;
|
|
||||||
|
|
||||||
var anonymousConnectionFailed = false;
|
|
||||||
connection.connect(jid, password, function (status, msg) {
|
|
||||||
console.log('Strophe status changed to', Strophe.getStatusString(status));
|
|
||||||
if (status === Strophe.Status.CONNECTED) {
|
|
||||||
if (config.useStunTurn) {
|
|
||||||
connection.jingle.getStunAndTurnCredentials();
|
|
||||||
}
|
|
||||||
document.getElementById('connect').disabled = true;
|
|
||||||
|
|
||||||
console.info("My Jabber ID: " + connection.jid);
|
|
||||||
|
|
||||||
if(password)
|
|
||||||
authenticatedUser = true;
|
|
||||||
maybeDoJoin();
|
|
||||||
} else if (status === Strophe.Status.CONNFAIL) {
|
|
||||||
if(msg === 'x-strophe-bad-non-anon-jid') {
|
|
||||||
anonymousConnectionFailed = true;
|
|
||||||
}
|
|
||||||
} else if (status === Strophe.Status.DISCONNECTED) {
|
|
||||||
if(anonymousConnectionFailed) {
|
|
||||||
// prompt user for username and password
|
|
||||||
$(document).trigger('passwordrequired.main');
|
|
||||||
}
|
|
||||||
} else if (status === Strophe.Status.AUTHFAIL) {
|
|
||||||
// wrong password or username, prompt user
|
|
||||||
$(document).trigger('passwordrequired.main');
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function maybeDoJoin() {
|
|
||||||
if (connection && connection.connected && Strophe.getResourceFromJid(connection.jid) // .connected is true while connecting?
|
|
||||||
&& (RTC.localAudio || RTC.localVideo)) {
|
|
||||||
doJoin();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function doJoin() {
|
|
||||||
if (!roomName) {
|
|
||||||
UI.generateRoomName();
|
|
||||||
}
|
|
||||||
|
|
||||||
Moderator.allocateConferenceFocus(
|
|
||||||
roomName, doJoinAfterFocus);
|
|
||||||
}
|
|
||||||
|
|
||||||
function doJoinAfterFocus() {
|
|
||||||
|
|
||||||
// Close authentication dialog if opened
|
|
||||||
if (authDialog) {
|
|
||||||
UI.messageHandler.closeDialog();
|
|
||||||
authDialog = null;
|
|
||||||
}
|
|
||||||
// Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
|
|
||||||
if (authRetryId) {
|
|
||||||
window.clearTimeout(authRetryId);
|
|
||||||
authRetryId = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var roomjid;
|
|
||||||
roomjid = roomName;
|
|
||||||
|
|
||||||
if (config.useNicks) {
|
|
||||||
var nick = window.prompt('Your nickname (optional)');
|
|
||||||
if (nick) {
|
|
||||||
roomjid += '/' + nick;
|
|
||||||
} else {
|
|
||||||
roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
var tmpJid = Strophe.getNodeFromJid(connection.jid);
|
|
||||||
|
|
||||||
if(!authenticatedUser)
|
|
||||||
tmpJid = tmpJid.substr(0, 8);
|
|
||||||
|
|
||||||
roomjid += '/' + tmpJid;
|
|
||||||
}
|
|
||||||
connection.emuc.doJoin(roomjid);
|
|
||||||
}
|
|
||||||
|
|
||||||
function waitForRemoteVideo(selector, ssrc, stream, jid) {
|
|
||||||
// XXX(gp) so, every call to this function is *always* preceded by a call
|
|
||||||
// to the RTC.attachMediaStream() function but that call is *not* followed
|
|
||||||
// by an update to the videoSrcToSsrc map!
|
|
||||||
//
|
|
||||||
// The above way of doing things results in video SRCs that don't correspond
|
|
||||||
// to any SSRC for a short period of time (to be more precise, for as long
|
|
||||||
// the waitForRemoteVideo takes to complete). This causes problems (see
|
|
||||||
// bellow).
|
|
||||||
//
|
|
||||||
// I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
|
|
||||||
// a second time in here and only then update the videoSrcToSsrc map? Why
|
|
||||||
// not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
|
|
||||||
// is called the first time? I actually do that in the lastN changed event
|
|
||||||
// handler because the "orphan" video SRC is causing troubles there. The
|
|
||||||
// purpose of this method would then be to fire the "videoactive.jingle".
|
|
||||||
//
|
|
||||||
// Food for though I guess :-)
|
|
||||||
|
|
||||||
if (selector.removed || !selector.parent().is(":visible")) {
|
|
||||||
console.warn("Media removed before had started", selector);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stream.id === 'mixedmslabel') return;
|
|
||||||
|
|
||||||
if (selector[0].currentTime > 0) {
|
|
||||||
var videoStream = simulcast.getReceivingVideoStream(stream);
|
|
||||||
RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
|
|
||||||
|
|
||||||
// FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
|
|
||||||
// in order to get rid of too many maps
|
|
||||||
if (ssrc && jid) {
|
|
||||||
jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc;
|
|
||||||
} else {
|
|
||||||
console.warn("No ssrc given for jid", jid);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).trigger('videoactive.jingle', [selector]);
|
|
||||||
} else {
|
|
||||||
setTimeout(function () {
|
|
||||||
waitForRemoteVideo(selector, ssrc, stream, jid);
|
|
||||||
}, 250);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).bind('remotestreamadded.jingle', function (event, data, sid) {
|
|
||||||
waitForPresence(data, sid);
|
|
||||||
});
|
|
||||||
|
|
||||||
function waitForPresence(data, sid) {
|
|
||||||
var sess = connection.jingle.sessions[sid];
|
|
||||||
|
|
||||||
var thessrc;
|
|
||||||
|
|
||||||
// look up an associated JID for a stream id
|
|
||||||
if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
|
|
||||||
// look only at a=ssrc: and _not_ at a=ssrc-group: lines
|
|
||||||
|
|
||||||
var ssrclines
|
|
||||||
= SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:');
|
|
||||||
ssrclines = ssrclines.filter(function (line) {
|
|
||||||
// NOTE(gp) previously we filtered on the mslabel, but that property
|
|
||||||
// is not always present.
|
|
||||||
// return line.indexOf('mslabel:' + data.stream.label) !== -1;
|
|
||||||
|
|
||||||
return ((line.indexOf('msid:' + data.stream.id) !== -1));
|
|
||||||
});
|
|
||||||
if (ssrclines.length) {
|
|
||||||
thessrc = ssrclines[0].substring(7).split(' ')[0];
|
|
||||||
|
|
||||||
// We signal our streams (through Jingle to the focus) before we set
|
|
||||||
// our presence (through which peers associate remote streams to
|
|
||||||
// jids). So, it might arrive that a remote stream is added but
|
|
||||||
// ssrc2jid is not yet updated and thus data.peerjid cannot be
|
|
||||||
// successfully set. Here we wait for up to a second for the
|
|
||||||
// presence to arrive.
|
|
||||||
|
|
||||||
if (!ssrc2jid[thessrc]) {
|
|
||||||
// TODO(gp) limit wait duration to 1 sec.
|
|
||||||
setTimeout(function(d, s) {
|
|
||||||
return function() {
|
|
||||||
waitForPresence(d, s);
|
|
||||||
}
|
|
||||||
}(data, sid), 250);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok to overwrite the one from focus? might save work in colibri.js
|
|
||||||
console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
|
|
||||||
if (ssrc2jid[thessrc]) {
|
|
||||||
data.peerjid = ssrc2jid[thessrc];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: this code should be removed when firefox implement multistream support
|
|
||||||
if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
|
|
||||||
{
|
|
||||||
if((notReceivedSSRCs.length == 0) ||
|
|
||||||
!ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
|
|
||||||
{
|
|
||||||
// TODO(gp) limit wait duration to 1 sec.
|
|
||||||
setTimeout(function(d, s) {
|
|
||||||
return function() {
|
|
||||||
waitForPresence(d, s);
|
|
||||||
}
|
|
||||||
}(data, sid), 250);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
thessrc = notReceivedSSRCs.pop();
|
|
||||||
if (ssrc2jid[thessrc]) {
|
|
||||||
data.peerjid = ssrc2jid[thessrc];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RTC.createRemoteStream(data, sid, thessrc);
|
|
||||||
|
|
||||||
var isVideo = data.stream.getVideoTracks().length > 0;
|
|
||||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
|
||||||
if (isVideo &&
|
|
||||||
data.peerjid && sess.peerjid === data.peerjid &&
|
|
||||||
data.stream.getVideoTracks().length === 0 &&
|
|
||||||
RTC.localVideo.getTracks().length > 0) {
|
|
||||||
//
|
|
||||||
window.setTimeout(function () {
|
|
||||||
sendKeyframe(sess.peerconnection);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
|
||||||
function sendKeyframe(pc) {
|
|
||||||
console.log('sendkeyframe', pc.iceConnectionState);
|
|
||||||
if (pc.iceConnectionState !== 'connected') return; // safe...
|
|
||||||
pc.setRemoteDescription(
|
|
||||||
pc.remoteDescription,
|
|
||||||
function () {
|
|
||||||
pc.createAnswer(
|
|
||||||
function (modifiedAnswer) {
|
|
||||||
pc.setLocalDescription(
|
|
||||||
modifiedAnswer,
|
|
||||||
function () {
|
|
||||||
// noop
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('triggerKeyframe setLocalDescription failed', error);
|
|
||||||
UI.messageHandler.showError();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('triggerKeyframe createAnswer failed', error);
|
|
||||||
UI.messageHandler.showError();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('triggerKeyframe setRemoteDescription failed', error);
|
|
||||||
UI.messageHandler.showError();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Really mute video, i.e. dont even send black frames
|
|
||||||
function muteVideo(pc, unmute) {
|
|
||||||
// FIXME: this probably needs another of those lovely state safeguards...
|
|
||||||
// which checks for iceconn == connected and sigstate == stable
|
|
||||||
pc.setRemoteDescription(pc.remoteDescription,
|
|
||||||
function () {
|
|
||||||
pc.createAnswer(
|
|
||||||
function (answer) {
|
|
||||||
var sdp = new SDP(answer.sdp);
|
|
||||||
if (sdp.media.length > 1) {
|
|
||||||
if (unmute)
|
|
||||||
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
|
|
||||||
else
|
|
||||||
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
|
|
||||||
sdp.raw = sdp.session + sdp.media.join('');
|
|
||||||
answer.sdp = sdp.raw;
|
|
||||||
}
|
|
||||||
pc.setLocalDescription(answer,
|
|
||||||
function () {
|
|
||||||
console.log('mute SLD ok');
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('mute SLD error');
|
|
||||||
UI.messageHandler.showError('Error',
|
|
||||||
'Oops! Something went wrong and we failed to ' +
|
|
||||||
'mute! (SLD Failure)');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log(error);
|
|
||||||
UI.messageHandler.showError();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('muteVideo SRD error');
|
|
||||||
UI.messageHandler.showError('Error',
|
|
||||||
'Oops! Something went wrong and we failed to stop video!' +
|
|
||||||
'(SRD Failure)');
|
|
||||||
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).bind('setLocalDescription.jingle', function (event, sid) {
|
|
||||||
// put our ssrcs into presence so other clients can identify our stream
|
|
||||||
var sess = connection.jingle.sessions[sid];
|
|
||||||
var newssrcs = [];
|
|
||||||
var media = simulcast.parseMedia(sess.peerconnection.localDescription);
|
|
||||||
media.forEach(function (media) {
|
|
||||||
|
|
||||||
if(Object.keys(media.sources).length > 0) {
|
|
||||||
// TODO(gp) maybe exclude FID streams?
|
|
||||||
Object.keys(media.sources).forEach(function (ssrc) {
|
|
||||||
newssrcs.push({
|
|
||||||
'ssrc': ssrc,
|
|
||||||
'type': media.type,
|
|
||||||
'direction': media.direction
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if(sess.localStreamsSSRC && sess.localStreamsSSRC[media.type])
|
|
||||||
{
|
|
||||||
newssrcs.push({
|
|
||||||
'ssrc': sess.localStreamsSSRC[media.type],
|
|
||||||
'type': media.type,
|
|
||||||
'direction': media.direction
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('new ssrcs', newssrcs);
|
|
||||||
|
|
||||||
// Have to clear presence map to get rid of removed streams
|
|
||||||
connection.emuc.clearPresenceMedia();
|
|
||||||
|
|
||||||
if (newssrcs.length > 0) {
|
|
||||||
for (var i = 1; i <= newssrcs.length; i ++) {
|
|
||||||
// Change video type to screen
|
|
||||||
if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) {
|
|
||||||
newssrcs[i-1].type = 'screen';
|
|
||||||
}
|
|
||||||
connection.emuc.addMediaToPresence(i,
|
|
||||||
newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.emuc.sendPresence();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).bind('iceconnectionstatechange.jingle', function (event, sid, session) {
|
|
||||||
switch (session.peerconnection.iceConnectionState) {
|
|
||||||
case 'checking':
|
|
||||||
session.timeChecking = (new Date()).getTime();
|
|
||||||
session.firstconnect = true;
|
|
||||||
break;
|
|
||||||
case 'completed': // on caller side
|
|
||||||
case 'connected':
|
|
||||||
if (session.firstconnect) {
|
|
||||||
session.firstconnect = false;
|
|
||||||
var metadata = {};
|
|
||||||
metadata.setupTime = (new Date()).getTime() - session.timeChecking;
|
|
||||||
session.peerconnection.getStats(function (res) {
|
|
||||||
if(res && res.result) {
|
|
||||||
res.result().forEach(function (report) {
|
|
||||||
if (report.type == 'googCandidatePair' && report.stat('googActiveConnection') == 'true') {
|
|
||||||
metadata.localCandidateType = report.stat('googLocalCandidateType');
|
|
||||||
metadata.remoteCandidateType = report.stat('googRemoteCandidateType');
|
|
||||||
|
|
||||||
// log pair as well so we can get nice pie charts
|
|
||||||
metadata.candidatePair = report.stat('googLocalCandidateType') + ';' + report.stat('googRemoteCandidateType');
|
|
||||||
|
|
||||||
if (report.stat('googRemoteAddress').indexOf('[') === 0) {
|
|
||||||
metadata.ipv6 = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).bind('presence.muc', function (event, jid, info, pres) {
|
|
||||||
|
|
||||||
//check if the video bridge is available
|
|
||||||
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
|
|
||||||
bridgeIsDown = true;
|
|
||||||
UI.messageHandler.showError("Error",
|
|
||||||
"Jitsi Videobridge is currently unavailable. Please try again later!");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.isFocus)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove old ssrcs coming from the jid
|
|
||||||
Object.keys(ssrc2jid).forEach(function (ssrc) {
|
|
||||||
if (ssrc2jid[ssrc] == jid) {
|
|
||||||
delete ssrc2jid[ssrc];
|
|
||||||
delete ssrc2videoType[ssrc];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
|
|
||||||
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
|
|
||||||
var ssrcV = ssrc.getAttribute('ssrc');
|
|
||||||
ssrc2jid[ssrcV] = jid;
|
|
||||||
notReceivedSSRCs.push(ssrcV);
|
|
||||||
|
|
||||||
var type = ssrc.getAttribute('type');
|
|
||||||
ssrc2videoType[ssrcV] = type;
|
|
||||||
|
|
||||||
// might need to update the direction if participant just went from sendrecv to recvonly
|
|
||||||
if (type === 'video' || type === 'screen') {
|
|
||||||
var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
|
|
||||||
switch (ssrc.getAttribute('direction')) {
|
|
||||||
case 'sendrecv':
|
|
||||||
el.show();
|
|
||||||
break;
|
|
||||||
case 'recvonly':
|
|
||||||
el.hide();
|
|
||||||
// FIXME: Check if we have to change large video
|
|
||||||
//VideoLayout.updateLargeVideo(el);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var displayName = !config.displayJids
|
|
||||||
? info.displayName : Strophe.getResourceFromJid(jid);
|
|
||||||
|
|
||||||
if (displayName && displayName.length > 0)
|
|
||||||
$(document).trigger('displaynamechanged',
|
|
||||||
[jid, displayName]);
|
|
||||||
/*if (focus !== null && info.displayName !== null) {
|
|
||||||
focus.setEndpointDisplayName(jid, info.displayName);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
//check if the video bridge is available
|
|
||||||
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
|
|
||||||
bridgeIsDown = true;
|
|
||||||
UI.messageHandler.showError("Error",
|
|
||||||
"Jitsi Videobridge is currently unavailable. Please try again later!");
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = $(pres).find('>userID').text();
|
|
||||||
var email = $(pres).find('>email');
|
|
||||||
if(email.length > 0) {
|
|
||||||
id = email.text();
|
|
||||||
}
|
|
||||||
UI.setUserAvatar(jid, id);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).bind('kicked.muc', function (event, jid) {
|
|
||||||
console.info(jid + " has been kicked from MUC!");
|
|
||||||
if (connection.emuc.myroomjid === jid) {
|
|
||||||
sessionTerminated = true;
|
|
||||||
disposeConference(false);
|
|
||||||
connection.emuc.doLeave();
|
|
||||||
UI.messageHandler.openMessageDialog("Session Terminated",
|
|
||||||
"Ouch! You have been kicked out of the meet!");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).bind('passwordrequired.main', function (event) {
|
|
||||||
console.log('password is required');
|
|
||||||
|
|
||||||
UI.messageHandler.openTwoButtonDialog(null,
|
|
||||||
'<h2>Password required</h2>' +
|
|
||||||
'<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
|
|
||||||
'<input id="passwordrequired.password" type="password" placeholder="user password">',
|
|
||||||
true,
|
|
||||||
"Ok",
|
|
||||||
function (e, v, m, f) {
|
|
||||||
if (v) {
|
|
||||||
var username = document.getElementById('passwordrequired.username');
|
|
||||||
var password = document.getElementById('passwordrequired.password');
|
|
||||||
|
|
||||||
if (username.value !== null && password.value != null) {
|
|
||||||
connect(username.value, password.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function (event) {
|
|
||||||
document.getElementById('passwordrequired.username').focus();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if video identified by given src is desktop stream.
|
|
||||||
* @param videoSrc eg.
|
|
||||||
* blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
function isVideoSrcDesktop(jid) {
|
|
||||||
// FIXME: fix this mapping mess...
|
|
||||||
// figure out if large video is desktop stream or just a camera
|
|
||||||
|
|
||||||
if(!jid)
|
|
||||||
return false;
|
|
||||||
var isDesktop = false;
|
|
||||||
if (connection.emuc.myroomjid &&
|
|
||||||
Strophe.getResourceFromJid(connection.emuc.myroomjid) === jid) {
|
|
||||||
// local video
|
|
||||||
isDesktop = desktopsharing.isUsingScreenStream();
|
|
||||||
} else {
|
|
||||||
// Do we have associations...
|
|
||||||
var videoSsrc = jid2Ssrc[jid];
|
|
||||||
if (videoSsrc) {
|
|
||||||
var videoType = ssrc2videoType[videoSsrc];
|
|
||||||
if (videoType) {
|
|
||||||
// Finally there...
|
|
||||||
isDesktop = videoType === 'screen';
|
|
||||||
} else {
|
|
||||||
console.error("No video type for ssrc: " + videoSsrc);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error("No ssrc for jid: " + jid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return isDesktop;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutes/unmutes the local video.
|
|
||||||
*
|
|
||||||
* @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
|
|
||||||
* @param options an object which specifies optional arguments such as the
|
|
||||||
* <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
|
|
||||||
* specifies whether the method was initiated in response to a user command (in
|
|
||||||
* contrast to an automatic decision taken by the application logic)
|
|
||||||
*/
|
|
||||||
function setVideoMute(mute, options) {
|
|
||||||
if (connection && RTC.localVideo) {
|
|
||||||
if (activecall) {
|
|
||||||
activecall.setVideoMute(
|
|
||||||
mute,
|
|
||||||
function (mute) {
|
|
||||||
var video = $('#video');
|
|
||||||
var communicativeClass = "icon-camera";
|
|
||||||
var muteClass = "icon-camera icon-camera-disabled";
|
|
||||||
|
|
||||||
if (mute) {
|
|
||||||
video.removeClass(communicativeClass);
|
|
||||||
video.addClass(muteClass);
|
|
||||||
} else {
|
|
||||||
video.removeClass(muteClass);
|
|
||||||
video.addClass(communicativeClass);
|
|
||||||
}
|
|
||||||
connection.emuc.addVideoInfoToPresence(mute);
|
|
||||||
connection.emuc.sendPresence();
|
|
||||||
},
|
|
||||||
options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).on('inlastnchanged', function (event, oldValue, newValue) {
|
|
||||||
if (config.muteLocalVideoIfNotInLastN) {
|
|
||||||
setVideoMute(!newValue, { 'byUser': false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutes/unmutes the local video.
|
|
||||||
*/
|
|
||||||
function toggleVideo() {
|
|
||||||
buttonClick("#video", "icon-camera icon-camera-disabled");
|
|
||||||
|
|
||||||
if (connection && activecall && RTC.localVideo ) {
|
|
||||||
setVideoMute(!RTC.localVideo.isMuted());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mutes / unmutes audio for the local participant.
|
|
||||||
*/
|
|
||||||
function toggleAudio() {
|
|
||||||
setAudioMuted(!RTC.localAudio.isMuted());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets muted audio state for the local participant.
|
|
||||||
*/
|
|
||||||
function setAudioMuted(mute) {
|
|
||||||
if (!(connection && RTC.localAudio)) {
|
|
||||||
preMuted = mute;
|
|
||||||
// We still click the button.
|
|
||||||
buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (forceMuted && !mute) {
|
|
||||||
console.info("Asking focus for unmute");
|
|
||||||
connection.moderate.setMute(connection.emuc.myroomjid, mute);
|
|
||||||
// FIXME: wait for result before resetting muted status
|
|
||||||
forceMuted = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mute == RTC.localAudio.isMuted()) {
|
|
||||||
// Nothing to do
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// It is not clear what is the right way to handle multiple tracks.
|
|
||||||
// So at least make sure that they are all muted or all unmuted and
|
|
||||||
// that we send presence just once.
|
|
||||||
RTC.localAudio.mute();
|
|
||||||
// isMuted is the opposite of audioEnabled
|
|
||||||
connection.emuc.addAudioInfoToPresence(mute);
|
|
||||||
connection.emuc.sendPresence();
|
|
||||||
UI.showLocalAudioIndicator(mute);
|
|
||||||
|
|
||||||
buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -706,60 +35,12 @@ $(document).ready(function () {
|
||||||
UI.start();
|
UI.start();
|
||||||
statistics.start();
|
statistics.start();
|
||||||
|
|
||||||
Moderator.init();
|
|
||||||
|
|
||||||
// Set default desktop sharing method
|
// Set default desktop sharing method
|
||||||
desktopsharing.init();
|
desktopsharing.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
$(window).bind('beforeunload', function () {
|
$(window).bind('beforeunload', function () {
|
||||||
if (connection && connection.connected) {
|
|
||||||
// ensure signout
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: config.bosh,
|
|
||||||
async: false,
|
|
||||||
cache: false,
|
|
||||||
contentType: 'application/xml',
|
|
||||||
data: "<body rid='" + (connection.rid || connection._proto.rid)
|
|
||||||
+ "' xmlns='http://jabber.org/protocol/httpbind' sid='"
|
|
||||||
+ (connection.sid || connection._proto.sid)
|
|
||||||
+ "' type='terminate'><presence xmlns='jabber:client' type='unavailable'/></body>",
|
|
||||||
success: function (data) {
|
|
||||||
console.log('signed out');
|
|
||||||
console.log(data);
|
|
||||||
},
|
|
||||||
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
|
||||||
console.log('signout error', textStatus + ' (' + errorThrown + ')');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
disposeConference(true);
|
|
||||||
if(API.isEnabled())
|
if(API.isEnabled())
|
||||||
API.dispose();
|
API.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
function disposeConference(onUnload) {
|
|
||||||
UI.onDisposeConference(onUnload);
|
|
||||||
var handler = activecall;
|
|
||||||
if (handler && handler.peerconnection) {
|
|
||||||
// FIXME: probably removing streams is not required and close() should
|
|
||||||
// be enough
|
|
||||||
if (RTC.localAudio) {
|
|
||||||
handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload);
|
|
||||||
}
|
|
||||||
if (RTC.localVideo) {
|
|
||||||
handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload);
|
|
||||||
}
|
|
||||||
handler.peerconnection.close();
|
|
||||||
}
|
|
||||||
statistics.onDisposeConference(onUnload);
|
|
||||||
activecall = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the style class of the element given by id.
|
|
||||||
*/
|
|
||||||
function buttonClick(id, classname) {
|
|
||||||
$(id).toggleClass(classname); // add the class to the clicked element
|
|
||||||
}
|
|
||||||
|
|
17
estos_log.js
17
estos_log.js
|
@ -1,17 +0,0 @@
|
||||||
/* global Strophe */
|
|
||||||
Strophe.addConnectionPlugin('logger', {
|
|
||||||
// logs raw stanzas and makes them available for download as JSON
|
|
||||||
connection: null,
|
|
||||||
log: [],
|
|
||||||
init: function (conn) {
|
|
||||||
this.connection = conn;
|
|
||||||
this.connection.rawInput = this.log_incoming.bind(this);
|
|
||||||
this.connection.rawOutput = this.log_outgoing.bind(this);
|
|
||||||
},
|
|
||||||
log_incoming: function (stanza) {
|
|
||||||
this.log.push([new Date().getTime(), 'incoming', stanza]);
|
|
||||||
},
|
|
||||||
log_outgoing: function (stanza) {
|
|
||||||
this.log.push([new Date().getTime(), 'outgoing', stanza]);
|
|
||||||
},
|
|
||||||
});
|
|
16
index.html
16
index.html
|
@ -11,16 +11,10 @@
|
||||||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||||
<script src="libs/jquery-2.1.1.min.js"></script>
|
<script src="libs/jquery-2.1.1.min.js"></script>
|
||||||
<script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
<script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
|
||||||
<script src="libs/strophe/strophe.jingle.adapter.js?v=4"></script><!-- strophe.jingle bundles -->
|
|
||||||
<script src="libs/strophe/strophe.min.js?v=1"></script>
|
<script src="libs/strophe/strophe.min.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
<script src="libs/strophe/strophe.disco.min.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
<script src="libs/strophe/strophe.caps.jsonly.min.js?v=1"></script>
|
||||||
<script src="libs/strophe/strophe.jingle.js?v=3"></script>
|
|
||||||
<script src="libs/strophe/strophe.jingle.sdp.js?v=5"></script>
|
|
||||||
<script src="libs/strophe/strophe.jingle.session.js?v=6"></script>
|
|
||||||
<script src="libs/strophe/strophe.util.js"></script>
|
|
||||||
<script src="libs/jquery-ui.js"></script>
|
<script src="libs/jquery-ui.js"></script>
|
||||||
<script src="libs/rayo.js?v=1"></script>
|
|
||||||
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
<script src="libs/tooltip.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||||
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
<script src="libs/popover.js?v=1"></script><!-- bootstrap tooltip lib -->
|
||||||
<script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
|
<script src="libs/pako.bundle.js?v=1"></script><!-- zlib deflate -->
|
||||||
|
@ -29,22 +23,20 @@
|
||||||
<script src="service/RTC/RTCBrowserType.js?v=1"></script>
|
<script src="service/RTC/RTCBrowserType.js?v=1"></script>
|
||||||
<script src="service/RTC/StreamEventTypes.js?v=2"></script>
|
<script src="service/RTC/StreamEventTypes.js?v=2"></script>
|
||||||
<script src="service/RTC/MediaStreamTypes.js?v=1"></script>
|
<script src="service/RTC/MediaStreamTypes.js?v=1"></script>
|
||||||
|
<script src="service/xmpp/XMPPEvents.js?v=1"></script>
|
||||||
<script src="service/desktopsharing/DesktopSharingEventTypes.js?v=1"></script>
|
<script src="service/desktopsharing/DesktopSharingEventTypes.js?v=1"></script>
|
||||||
<script src="libs/modules/simulcast.bundle.js?v=3"></script>
|
<script src="libs/modules/simulcast.bundle.js?v=3"></script>
|
||||||
<script src="libs/modules/connectionquality.bundle.js?v=1"></script>
|
<script src="libs/modules/connectionquality.bundle.js?v=1"></script>
|
||||||
<script src="libs/modules/UI.bundle.js?v=5"></script>
|
<script src="libs/modules/UI.bundle.js?v=5"></script>
|
||||||
<script src="libs/modules/statistics.bundle.js?v=1"></script>
|
<script src="libs/modules/statistics.bundle.js?v=1"></script>
|
||||||
<script src="libs/modules/RTC.bundle.js?v=4"></script>
|
<script src="libs/modules/RTC.bundle.js?v=4"></script>
|
||||||
<script src="muc.js?v=17"></script><!-- simple MUC library -->
|
|
||||||
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
|
|
||||||
<script src="libs/modules/desktopsharing.bundle.js?v=3"></script><!-- desktop sharing -->
|
<script src="libs/modules/desktopsharing.bundle.js?v=3"></script><!-- desktop sharing -->
|
||||||
|
<script src="util.js?v=7"></script><!-- utility functions -->
|
||||||
|
<script src="libs/modules/xmpp.bundle.js?v=1"></script>
|
||||||
<script src="app.js?v=26"></script><!-- application logic -->
|
<script src="app.js?v=26"></script><!-- application logic -->
|
||||||
<script src="libs/modules/API.bundle.js?v=1"></script>
|
<script src="libs/modules/API.bundle.js?v=1"></script>
|
||||||
<script src="util.js?v=7"></script><!-- utility functions -->
|
|
||||||
<script src="moderatemuc.js?v=4"></script><!-- moderator plugin -->
|
|
||||||
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
|
||||||
<script src="moderator.js?v=2"></script><!-- media stream -->
|
|
||||||
<script src="tracking.js?v=1"></script><!-- tracking -->
|
|
||||||
<script src="keyboard_shortcut.js?v=4"></script>
|
<script src="keyboard_shortcut.js?v=4"></script>
|
||||||
<link rel="stylesheet" href="css/font.css?v=6"/>
|
<link rel="stylesheet" href="css/font.css?v=6"/>
|
||||||
<link rel="stylesheet" href="css/toastr.css?v=1">
|
<link rel="stylesheet" href="css/toastr.css?v=1">
|
||||||
|
|
|
@ -14,20 +14,20 @@ var KeyboardShortcut = (function(my) {
|
||||||
77: {
|
77: {
|
||||||
character: "M",
|
character: "M",
|
||||||
id: "mutePopover",
|
id: "mutePopover",
|
||||||
function: toggleAudio
|
function: UI.toggleAudio
|
||||||
},
|
},
|
||||||
84: {
|
84: {
|
||||||
character: "T",
|
character: "T",
|
||||||
function: function() {
|
function: function() {
|
||||||
if(!RTC.localAudio.isMuted()) {
|
if(!RTC.localAudio.isMuted()) {
|
||||||
toggleAudio();
|
UI.toggleAudio();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
86: {
|
86: {
|
||||||
character: "V",
|
character: "V",
|
||||||
id: "toggleVideoPopover",
|
id: "toggleVideoPopover",
|
||||||
function: toggleVideo
|
function: UI.toggleVideo
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ var KeyboardShortcut = (function(my) {
|
||||||
if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
|
if(!($(":focus").is("input[type=text]") || $(":focus").is("input[type=password]") || $(":focus").is("textarea"))) {
|
||||||
if(e.which === "T".charCodeAt(0)) {
|
if(e.which === "T".charCodeAt(0)) {
|
||||||
if(RTC.localAudio.isMuted()) {
|
if(RTC.localAudio.isMuted()) {
|
||||||
toggleAudio();
|
UI.toggleAudio();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
103
libs/rayo.js
103
libs/rayo.js
|
@ -1,103 +0,0 @@
|
||||||
/* jshint -W117 */
|
|
||||||
Strophe.addConnectionPlugin('rayo',
|
|
||||||
{
|
|
||||||
RAYO_XMLNS: 'urn:xmpp:rayo:1',
|
|
||||||
connection: null,
|
|
||||||
init: function (conn)
|
|
||||||
{
|
|
||||||
this.connection = conn;
|
|
||||||
if (this.connection.disco)
|
|
||||||
{
|
|
||||||
this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.addHandler(
|
|
||||||
this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
|
|
||||||
},
|
|
||||||
onRayo: function (iq)
|
|
||||||
{
|
|
||||||
console.info("Rayo IQ", iq);
|
|
||||||
},
|
|
||||||
dial: function (to, from, roomName, roomPass)
|
|
||||||
{
|
|
||||||
var self = this;
|
|
||||||
var req = $iq(
|
|
||||||
{
|
|
||||||
type: 'set',
|
|
||||||
to: focusMucJid
|
|
||||||
}
|
|
||||||
);
|
|
||||||
req.c('dial',
|
|
||||||
{
|
|
||||||
xmlns: this.RAYO_XMLNS,
|
|
||||||
to: to,
|
|
||||||
from: from
|
|
||||||
});
|
|
||||||
req.c('header',
|
|
||||||
{
|
|
||||||
name: 'JvbRoomName',
|
|
||||||
value: roomName
|
|
||||||
}).up();
|
|
||||||
|
|
||||||
if (roomPass !== null && roomPass.length) {
|
|
||||||
|
|
||||||
req.c('header',
|
|
||||||
{
|
|
||||||
name: 'JvbRoomPassword',
|
|
||||||
value: roomPass
|
|
||||||
}).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.sendIQ(
|
|
||||||
req,
|
|
||||||
function (result)
|
|
||||||
{
|
|
||||||
console.info('Dial result ', result);
|
|
||||||
|
|
||||||
var resource = $(result).find('ref').attr('uri');
|
|
||||||
this.call_resource = resource.substr('xmpp:'.length);
|
|
||||||
console.info(
|
|
||||||
"Received call resource: " + this.call_resource);
|
|
||||||
},
|
|
||||||
function (error)
|
|
||||||
{
|
|
||||||
console.info('Dial error ', error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
hang_up: function ()
|
|
||||||
{
|
|
||||||
if (!this.call_resource)
|
|
||||||
{
|
|
||||||
console.warn("No call in progress");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var self = this;
|
|
||||||
var req = $iq(
|
|
||||||
{
|
|
||||||
type: 'set',
|
|
||||||
to: this.call_resource
|
|
||||||
}
|
|
||||||
);
|
|
||||||
req.c('hangup',
|
|
||||||
{
|
|
||||||
xmlns: this.RAYO_XMLNS
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.sendIQ(
|
|
||||||
req,
|
|
||||||
function (result)
|
|
||||||
{
|
|
||||||
console.info('Hangup result ', result);
|
|
||||||
self.call_resource = null;
|
|
||||||
},
|
|
||||||
function (error)
|
|
||||||
{
|
|
||||||
console.info('Hangup error ', error);
|
|
||||||
self.call_resource = null;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
|
@ -1,327 +0,0 @@
|
||||||
/* jshint -W117 */
|
|
||||||
|
|
||||||
|
|
||||||
function CallIncomingJingle(sid) {
|
|
||||||
var sess = connection.jingle.sessions[sid];
|
|
||||||
|
|
||||||
// TODO: do we check activecall == null?
|
|
||||||
activecall = sess;
|
|
||||||
|
|
||||||
statistics.onConferenceCreated(sess);
|
|
||||||
RTC.onConferenceCreated(sess);
|
|
||||||
|
|
||||||
// TODO: check affiliation and/or role
|
|
||||||
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
|
|
||||||
sess.usedrip = true; // not-so-naive trickle ice
|
|
||||||
sess.sendAnswer();
|
|
||||||
sess.accept();
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
Strophe.addConnectionPlugin('jingle', {
|
|
||||||
connection: null,
|
|
||||||
sessions: {},
|
|
||||||
jid2session: {},
|
|
||||||
ice_config: {iceServers: []},
|
|
||||||
pc_constraints: {},
|
|
||||||
media_constraints: {
|
|
||||||
mandatory: {
|
|
||||||
'OfferToReceiveAudio': true,
|
|
||||||
'OfferToReceiveVideo': true
|
|
||||||
}
|
|
||||||
// MozDontOfferDataChannel: true when this is firefox
|
|
||||||
},
|
|
||||||
init: function (conn) {
|
|
||||||
this.connection = conn;
|
|
||||||
if (this.connection.disco) {
|
|
||||||
// http://xmpp.org/extensions/xep-0167.html#support
|
|
||||||
// http://xmpp.org/extensions/xep-0176.html#support
|
|
||||||
this.connection.disco.addFeature('urn:xmpp:jingle:1');
|
|
||||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
|
|
||||||
this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
|
|
||||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
|
|
||||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
|
|
||||||
|
|
||||||
|
|
||||||
// this is dealt with by SDP O/A so we don't need to annouce this
|
|
||||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
|
|
||||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
|
|
||||||
if (config.useRtcpMux) {
|
|
||||||
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
|
|
||||||
}
|
|
||||||
if (config.useBundle) {
|
|
||||||
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
|
|
||||||
}
|
|
||||||
//this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
|
|
||||||
}
|
|
||||||
this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
|
|
||||||
},
|
|
||||||
onJingle: function (iq) {
|
|
||||||
var sid = $(iq).find('jingle').attr('sid');
|
|
||||||
var action = $(iq).find('jingle').attr('action');
|
|
||||||
var fromJid = iq.getAttribute('from');
|
|
||||||
// send ack first
|
|
||||||
var ack = $iq({type: 'result',
|
|
||||||
to: fromJid,
|
|
||||||
id: iq.getAttribute('id')
|
|
||||||
});
|
|
||||||
console.log('on jingle ' + action + ' from ' + fromJid, iq);
|
|
||||||
var sess = this.sessions[sid];
|
|
||||||
if ('session-initiate' != action) {
|
|
||||||
if (sess === null) {
|
|
||||||
ack.type = 'error';
|
|
||||||
ack.c('error', {type: 'cancel'})
|
|
||||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
|
||||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
|
||||||
this.connection.send(ack);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
|
|
||||||
// local jid is not checked
|
|
||||||
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
|
|
||||||
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
|
|
||||||
ack.type = 'error';
|
|
||||||
ack.c('error', {type: 'cancel'})
|
|
||||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
|
||||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
|
||||||
this.connection.send(ack);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else if (sess !== undefined) {
|
|
||||||
// existing session with same session id
|
|
||||||
// this might be out-of-order if the sess.peerjid is the same as from
|
|
||||||
ack.type = 'error';
|
|
||||||
ack.c('error', {type: 'cancel'})
|
|
||||||
.c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
|
|
||||||
console.warn('duplicate session id', sid);
|
|
||||||
this.connection.send(ack);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// FIXME: check for a defined action
|
|
||||||
this.connection.send(ack);
|
|
||||||
// see http://xmpp.org/extensions/xep-0166.html#concepts-session
|
|
||||||
switch (action) {
|
|
||||||
case 'session-initiate':
|
|
||||||
sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
|
|
||||||
// configure session
|
|
||||||
|
|
||||||
sess.media_constraints = this.media_constraints;
|
|
||||||
sess.pc_constraints = this.pc_constraints;
|
|
||||||
sess.ice_config = this.ice_config;
|
|
||||||
|
|
||||||
sess.initiate(fromJid, false);
|
|
||||||
// FIXME: setRemoteDescription should only be done when this call is to be accepted
|
|
||||||
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
|
|
||||||
|
|
||||||
this.sessions[sess.sid] = sess;
|
|
||||||
this.jid2session[sess.peerjid] = sess;
|
|
||||||
|
|
||||||
// the callback should either
|
|
||||||
// .sendAnswer and .accept
|
|
||||||
// or .sendTerminate -- not necessarily synchronus
|
|
||||||
CallIncomingJingle(sess.sid);
|
|
||||||
break;
|
|
||||||
case 'session-accept':
|
|
||||||
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
|
|
||||||
sess.accept();
|
|
||||||
$(document).trigger('callaccepted.jingle', [sess.sid]);
|
|
||||||
break;
|
|
||||||
case 'session-terminate':
|
|
||||||
// If this is not the focus sending the terminate, we have
|
|
||||||
// nothing more to do here.
|
|
||||||
if (Object.keys(this.sessions).length < 1
|
|
||||||
|| !(this.sessions[Object.keys(this.sessions)[0]]
|
|
||||||
instanceof JingleSession))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
console.log('terminating...', sess.sid);
|
|
||||||
sess.terminate();
|
|
||||||
this.terminate(sess.sid);
|
|
||||||
if ($(iq).find('>jingle>reason').length) {
|
|
||||||
$(document).trigger('callterminated.jingle', [
|
|
||||||
sess.sid,
|
|
||||||
sess.peerjid,
|
|
||||||
$(iq).find('>jingle>reason>:first')[0].tagName,
|
|
||||||
$(iq).find('>jingle>reason>text').text()
|
|
||||||
]);
|
|
||||||
} else {
|
|
||||||
$(document).trigger('callterminated.jingle',
|
|
||||||
[sess.sid, sess.peerjid]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'transport-info':
|
|
||||||
sess.addIceCandidate($(iq).find('>jingle>content'));
|
|
||||||
break;
|
|
||||||
case 'session-info':
|
|
||||||
var affected;
|
|
||||||
if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
|
||||||
$(document).trigger('ringing.jingle', [sess.sid]);
|
|
||||||
} else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
|
||||||
affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
|
||||||
$(document).trigger('mute.jingle', [sess.sid, affected]);
|
|
||||||
} else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
|
||||||
affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
|
||||||
$(document).trigger('unmute.jingle', [sess.sid, affected]);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'addsource': // FIXME: proprietary, un-jingleish
|
|
||||||
case 'source-add': // FIXME: proprietary
|
|
||||||
sess.addSource($(iq).find('>jingle>content'), fromJid);
|
|
||||||
break;
|
|
||||||
case 'removesource': // FIXME: proprietary, un-jingleish
|
|
||||||
case 'source-remove': // FIXME: proprietary
|
|
||||||
sess.removeSource($(iq).find('>jingle>content'), fromJid);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.warn('jingle action not implemented', action);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
|
|
||||||
var sess = new JingleSession(myjid || this.connection.jid,
|
|
||||||
Math.random().toString(36).substr(2, 12), // random string
|
|
||||||
this.connection);
|
|
||||||
// configure session
|
|
||||||
|
|
||||||
sess.media_constraints = this.media_constraints;
|
|
||||||
sess.pc_constraints = this.pc_constraints;
|
|
||||||
sess.ice_config = this.ice_config;
|
|
||||||
|
|
||||||
sess.initiate(peerjid, true);
|
|
||||||
this.sessions[sess.sid] = sess;
|
|
||||||
this.jid2session[sess.peerjid] = sess;
|
|
||||||
sess.sendOffer();
|
|
||||||
return sess;
|
|
||||||
},
|
|
||||||
terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
|
|
||||||
if (sid === null || sid === undefined) {
|
|
||||||
for (sid in this.sessions) {
|
|
||||||
if (this.sessions[sid].state != 'ended') {
|
|
||||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
|
||||||
this.sessions[sid].terminate();
|
|
||||||
}
|
|
||||||
delete this.jid2session[this.sessions[sid].peerjid];
|
|
||||||
delete this.sessions[sid];
|
|
||||||
}
|
|
||||||
} else if (this.sessions.hasOwnProperty(sid)) {
|
|
||||||
if (this.sessions[sid].state != 'ended') {
|
|
||||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
|
||||||
this.sessions[sid].terminate();
|
|
||||||
}
|
|
||||||
delete this.jid2session[this.sessions[sid].peerjid];
|
|
||||||
delete this.sessions[sid];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Used to terminate a session when an unavailable presence is received.
|
|
||||||
terminateByJid: function (jid) {
|
|
||||||
if (this.jid2session.hasOwnProperty(jid)) {
|
|
||||||
var sess = this.jid2session[jid];
|
|
||||||
if (sess) {
|
|
||||||
sess.terminate();
|
|
||||||
console.log('peer went away silently', jid);
|
|
||||||
delete this.sessions[sess.sid];
|
|
||||||
delete this.jid2session[jid];
|
|
||||||
$(document).trigger('callterminated.jingle',
|
|
||||||
[sess.sid, jid], 'gone');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
terminateRemoteByJid: function (jid, reason) {
|
|
||||||
if (this.jid2session.hasOwnProperty(jid)) {
|
|
||||||
var sess = this.jid2session[jid];
|
|
||||||
if (sess) {
|
|
||||||
sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
|
|
||||||
sess.terminate();
|
|
||||||
console.log('terminate peer with jid', sess.sid, jid);
|
|
||||||
delete this.sessions[sess.sid];
|
|
||||||
delete this.jid2session[jid];
|
|
||||||
$(document).trigger('callterminated.jingle',
|
|
||||||
[sess.sid, jid, 'kicked']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getStunAndTurnCredentials: function () {
|
|
||||||
// get stun and turn configuration from server via xep-0215
|
|
||||||
// uses time-limited credentials as described in
|
|
||||||
// http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
|
|
||||||
//
|
|
||||||
// see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
|
|
||||||
// for a prosody module which implements this
|
|
||||||
//
|
|
||||||
// currently, this doesn't work with updateIce and therefore credentials with a long
|
|
||||||
// validity have to be fetched before creating the peerconnection
|
|
||||||
// TODO: implement refresh via updateIce as described in
|
|
||||||
// https://code.google.com/p/webrtc/issues/detail?id=1650
|
|
||||||
var self = this;
|
|
||||||
this.connection.sendIQ(
|
|
||||||
$iq({type: 'get', to: this.connection.domain})
|
|
||||||
.c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
|
|
||||||
function (res) {
|
|
||||||
var iceservers = [];
|
|
||||||
$(res).find('>services>service').each(function (idx, el) {
|
|
||||||
el = $(el);
|
|
||||||
var dict = {};
|
|
||||||
var type = el.attr('type');
|
|
||||||
switch (type) {
|
|
||||||
case 'stun':
|
|
||||||
dict.url = 'stun:' + el.attr('host');
|
|
||||||
if (el.attr('port')) {
|
|
||||||
dict.url += ':' + el.attr('port');
|
|
||||||
}
|
|
||||||
iceservers.push(dict);
|
|
||||||
break;
|
|
||||||
case 'turn':
|
|
||||||
case 'turns':
|
|
||||||
dict.url = type + ':';
|
|
||||||
if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
|
|
||||||
if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
|
|
||||||
dict.url += el.attr('username') + '@';
|
|
||||||
} else {
|
|
||||||
dict.username = el.attr('username'); // only works in M28
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dict.url += el.attr('host');
|
|
||||||
if (el.attr('port') && el.attr('port') != '3478') {
|
|
||||||
dict.url += ':' + el.attr('port');
|
|
||||||
}
|
|
||||||
if (el.attr('transport') && el.attr('transport') != 'udp') {
|
|
||||||
dict.url += '?transport=' + el.attr('transport');
|
|
||||||
}
|
|
||||||
if (el.attr('password')) {
|
|
||||||
dict.credential = el.attr('password');
|
|
||||||
}
|
|
||||||
iceservers.push(dict);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
self.ice_config.iceServers = iceservers;
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
console.warn('getting turn credentials failed', err);
|
|
||||||
console.warn('is mod_turncredentials or similar installed?');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
// implement push?
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the log data
|
|
||||||
*/
|
|
||||||
populateData: function () {
|
|
||||||
var data = {};
|
|
||||||
Object.keys(this.sessions).forEach(function (sid) {
|
|
||||||
var session = this.sessions[sid];
|
|
||||||
if (session.peerconnection && session.peerconnection.updateLog) {
|
|
||||||
// FIXME: should probably be a .dump call
|
|
||||||
data["jingle_" + session.sid] = {
|
|
||||||
updateLog: session.peerconnection.updateLog,
|
|
||||||
stats: session.peerconnection.stats,
|
|
||||||
url: window.location.href
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,41 +0,0 @@
|
||||||
/**
|
|
||||||
* Strophe logger implementation. Logs from level WARN and above.
|
|
||||||
*/
|
|
||||||
Strophe.log = function (level, msg) {
|
|
||||||
switch(level) {
|
|
||||||
case Strophe.LogLevel.WARN:
|
|
||||||
console.warn("Strophe: "+msg);
|
|
||||||
break;
|
|
||||||
case Strophe.LogLevel.ERROR:
|
|
||||||
case Strophe.LogLevel.FATAL:
|
|
||||||
console.error("Strophe: "+msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Strophe.getStatusString = function(status)
|
|
||||||
{
|
|
||||||
switch (status)
|
|
||||||
{
|
|
||||||
case Strophe.Status.ERROR:
|
|
||||||
return "ERROR";
|
|
||||||
case Strophe.Status.CONNECTING:
|
|
||||||
return "CONNECTING";
|
|
||||||
case Strophe.Status.CONNFAIL:
|
|
||||||
return "CONNFAIL";
|
|
||||||
case Strophe.Status.AUTHENTICATING:
|
|
||||||
return "AUTHENTICATING";
|
|
||||||
case Strophe.Status.AUTHFAIL:
|
|
||||||
return "AUTHFAIL";
|
|
||||||
case Strophe.Status.CONNECTED:
|
|
||||||
return "CONNECTED";
|
|
||||||
case Strophe.Status.DISCONNECTED:
|
|
||||||
return "DISCONNECTED";
|
|
||||||
case Strophe.Status.DISCONNECTING:
|
|
||||||
return "DISCONNECTING";
|
|
||||||
case Strophe.Status.ATTACHED:
|
|
||||||
return "ATTACHED";
|
|
||||||
default:
|
|
||||||
return "unknown";
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,56 +0,0 @@
|
||||||
/* global $, $iq, config, connection, focusMucJid, forceMuted,
|
|
||||||
setAudioMuted, Strophe, toggleAudio */
|
|
||||||
/**
|
|
||||||
* Moderate connection plugin.
|
|
||||||
*/
|
|
||||||
Strophe.addConnectionPlugin('moderate', {
|
|
||||||
connection: null,
|
|
||||||
init: function (conn) {
|
|
||||||
this.connection = conn;
|
|
||||||
|
|
||||||
this.connection.addHandler(this.onMute.bind(this),
|
|
||||||
'http://jitsi.org/jitmeet/audio',
|
|
||||||
'iq',
|
|
||||||
'set',
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
},
|
|
||||||
setMute: function (jid, mute) {
|
|
||||||
console.info("set mute", mute);
|
|
||||||
var iqToFocus = $iq({to: focusMucJid, type: 'set'})
|
|
||||||
.c('mute', {
|
|
||||||
xmlns: 'http://jitsi.org/jitmeet/audio',
|
|
||||||
jid: jid
|
|
||||||
})
|
|
||||||
.t(mute.toString())
|
|
||||||
.up();
|
|
||||||
|
|
||||||
this.connection.sendIQ(
|
|
||||||
iqToFocus,
|
|
||||||
function (result) {
|
|
||||||
console.log('set mute', result);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('set mute error', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onMute: function (iq) {
|
|
||||||
var from = iq.getAttribute('from');
|
|
||||||
if (from !== focusMucJid) {
|
|
||||||
console.warn("Ignored mute from non focus peer");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var mute = $(iq).find('mute');
|
|
||||||
if (mute.length) {
|
|
||||||
var doMuteAudio = mute.text() === "true";
|
|
||||||
setAudioMuted(doMuteAudio);
|
|
||||||
forceMuted = doMuteAudio;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
eject: function (jid) {
|
|
||||||
// We're not the focus, so can't terminate
|
|
||||||
//connection.jingle.terminateRemoteByJid(jid, 'kick');
|
|
||||||
connection.emuc.kick(jid);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -18,8 +18,8 @@
|
||||||
var commands =
|
var commands =
|
||||||
{
|
{
|
||||||
displayName: UI.inputDisplayNameHandler,
|
displayName: UI.inputDisplayNameHandler,
|
||||||
muteAudio: toggleAudio,
|
muteAudio: UI.toggleAudio,
|
||||||
muteVideo: toggleVideo,
|
muteVideo: UI.toggleVideo,
|
||||||
toggleFilmStrip: UI.toggleFilmStrip,
|
toggleFilmStrip: UI.toggleFilmStrip,
|
||||||
toggleChat: UI.toggleChat,
|
toggleChat: UI.toggleChat,
|
||||||
toggleContactList: UI.toggleContactList
|
toggleContactList: UI.toggleContactList
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
|
/* global Strophe, updateLargeVideo, focusedVideoSrc*/
|
||||||
|
|
||||||
// cache datachannels to avoid garbage collection
|
// cache datachannels to avoid garbage collection
|
||||||
// https://code.google.com/p/chromium/issues/detail?id=405545
|
// https://code.google.com/p/chromium/issues/detail?id=405545
|
||||||
|
@ -91,7 +91,7 @@ var DataChannels =
|
||||||
newValue = new Boolean(newValue).valueOf();
|
newValue = new Boolean(newValue).valueOf();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$(document).trigger('inlastnchanged', [oldValue, newValue]);
|
UI.onLastNChanged(oldValue, newValue);
|
||||||
}
|
}
|
||||||
else if ("LastNEndpointsChangeEvent" === colibriClass)
|
else if ("LastNEndpointsChangeEvent" === colibriClass)
|
||||||
{
|
{
|
||||||
|
|
|
@ -58,7 +58,7 @@ var RTC = {
|
||||||
createRemoteStream: function (data, sid, thessrc) {
|
createRemoteStream: function (data, sid, thessrc) {
|
||||||
var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter,
|
var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter,
|
||||||
this.getBrowserType());
|
this.getBrowserType());
|
||||||
var jid = data.peerjid || connection.emuc.myroomjid;
|
var jid = data.peerjid || xmpp.myJid();
|
||||||
if(!this.remoteStreams[jid]) {
|
if(!this.remoteStreams[jid]) {
|
||||||
this.remoteStreams[jid] = {};
|
this.remoteStreams[jid] = {};
|
||||||
}
|
}
|
||||||
|
@ -144,16 +144,7 @@ var RTC = {
|
||||||
RTC.localVideo = this.createLocalStream(stream, type, true);
|
RTC.localVideo = this.createLocalStream(stream, type, true);
|
||||||
// Stop the stream to trigger onended event for old stream
|
// Stop the stream to trigger onended event for old stream
|
||||||
oldStream.stop();
|
oldStream.stop();
|
||||||
if (activecall) {
|
xmpp.switchStreams(stream, oldStream,callback);
|
||||||
// FIXME: will block switchInProgress on true value in case of exception
|
|
||||||
activecall.switchStreams(stream, oldStream, callback);
|
|
||||||
} else {
|
|
||||||
// We are done immediately
|
|
||||||
console.error("No conference handler");
|
|
||||||
UI.messageHandler.showError('Error',
|
|
||||||
'Unable to switch video stream.');
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
258
modules/UI/UI.js
258
modules/UI/UI.js
|
@ -17,9 +17,11 @@ var PanelToggler = require("./side_pannels/SidePanelToggler");
|
||||||
var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
|
var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
|
||||||
UI.messageHandler = require("./util/MessageHandler");
|
UI.messageHandler = require("./util/MessageHandler");
|
||||||
var messageHandler = UI.messageHandler;
|
var messageHandler = UI.messageHandler;
|
||||||
|
var Authentication = require("./authentication/Authentication");
|
||||||
|
var UIUtil = require("./util/UIUtil");
|
||||||
|
|
||||||
//var eventEmitter = new EventEmitter();
|
//var eventEmitter = new EventEmitter();
|
||||||
|
var roomName = null;
|
||||||
|
|
||||||
|
|
||||||
function setupPrezi()
|
function setupPrezi()
|
||||||
|
@ -39,7 +41,7 @@ function setupChat()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupToolbars() {
|
function setupToolbars() {
|
||||||
Toolbar.init();
|
Toolbar.init(UI);
|
||||||
Toolbar.setupButtonsFromConfig();
|
Toolbar.setupButtonsFromConfig();
|
||||||
BottomToolbar.init();
|
BottomToolbar.init();
|
||||||
}
|
}
|
||||||
|
@ -62,6 +64,16 @@ function streamHandler(stream) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDisposeConference(unload) {
|
||||||
|
Toolbar.showAuthenticateButton(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
function onDisplayNameChanged(jid, displayName) {
|
||||||
|
ContactList.onDisplayNameChange(jid, displayName);
|
||||||
|
SettingsMenu.onDisplayNameChange(jid, displayName);
|
||||||
|
VideoLayout.onDisplayNameChanged(jid, displayName);
|
||||||
|
}
|
||||||
|
|
||||||
function registerListeners() {
|
function registerListeners() {
|
||||||
RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||||
|
|
||||||
|
@ -70,14 +82,7 @@ function registerListeners() {
|
||||||
VideoLayout.onRemoteStreamAdded(stream);
|
VideoLayout.onRemoteStreamAdded(stream);
|
||||||
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
|
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
|
||||||
|
|
||||||
// Listen for large video size updates
|
VideoLayout.init();
|
||||||
document.getElementById('largeVideo')
|
|
||||||
.addEventListener('loadedmetadata', function (e) {
|
|
||||||
currentVideoWidth = this.videoWidth;
|
|
||||||
currentVideoHeight = this.videoHeight;
|
|
||||||
VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
statistics.addAudioLevelListener(function(jid, audioLevel)
|
statistics.addAudioLevelListener(function(jid, audioLevel)
|
||||||
{
|
{
|
||||||
|
@ -104,8 +109,38 @@ function registerListeners() {
|
||||||
desktopsharing.addListener(
|
desktopsharing.addListener(
|
||||||
Toolbar.changeDesktopSharingButtonState,
|
Toolbar.changeDesktopSharingButtonState,
|
||||||
DesktopSharingEventTypes.SWITCHING_DONE);
|
DesktopSharingEventTypes.SWITCHING_DONE);
|
||||||
|
xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
|
||||||
|
xmpp.addListener(XMPPEvents.KICKED, function () {
|
||||||
|
messageHandler.openMessageDialog("Session Terminated",
|
||||||
|
"Ouch! You have been kicked out of the meet!");
|
||||||
|
});
|
||||||
|
xmpp.addListener(XMPPEvents.BRIDGE_DOWN, function () {
|
||||||
|
messageHandler.showError("Error",
|
||||||
|
"Jitsi Videobridge is currently unavailable. Please try again later!");
|
||||||
|
});
|
||||||
|
xmpp.addListener(XMPPEvents.USER_ID_CHANGED, Avatar.setUserAvatar);
|
||||||
|
xmpp.addListener(XMPPEvents.CHANGED_STREAMS, function (jid, changedStreams) {
|
||||||
|
for(stream in changedStreams)
|
||||||
|
{
|
||||||
|
// might need to update the direction if participant just went from sendrecv to recvonly
|
||||||
|
if (stream.type === 'video' || stream.type === 'screen') {
|
||||||
|
var el = $('#participant_' + Strophe.getResourceFromJid(jid) + '>video');
|
||||||
|
switch (stream.direction) {
|
||||||
|
case 'sendrecv':
|
||||||
|
el.show();
|
||||||
|
break;
|
||||||
|
case 'recvonly':
|
||||||
|
el.hide();
|
||||||
|
// FIXME: Check if we have to change large video
|
||||||
|
//VideoLayout.updateLargeVideo(el);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
xmpp.addListener(XMPPEvents.DISPLAY_NAME_CHANGED, onDisplayNameChanged);
|
||||||
|
xmpp.addListener(XMPPEvents.MUC_JOINED, onMucJoined);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindEvents()
|
function bindEvents()
|
||||||
|
@ -117,10 +152,6 @@ function bindEvents()
|
||||||
function () {
|
function () {
|
||||||
VideoLayout.resizeLargeVideoContainer();
|
VideoLayout.resizeLargeVideoContainer();
|
||||||
VideoLayout.positionLarge();
|
VideoLayout.positionLarge();
|
||||||
isFullScreen = document.fullScreen ||
|
|
||||||
document.mozFullScreen ||
|
|
||||||
document.webkitIsFullScreen;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -255,11 +286,6 @@ UI.start = function () {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
UI.setUserAvatar = function (jid, id) {
|
|
||||||
Avatar.setUserAvatar(jid, id);
|
|
||||||
};
|
|
||||||
|
|
||||||
UI.toggleSmileys = function () {
|
UI.toggleSmileys = function () {
|
||||||
Chat.toggleSmileys();
|
Chat.toggleSmileys();
|
||||||
};
|
};
|
||||||
|
@ -278,7 +304,7 @@ UI.updateChatConversation = function (from, displayName, message) {
|
||||||
return Chat.updateChatConversation(from, displayName, message);
|
return Chat.updateChatConversation(from, displayName, message);
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.onMucJoined = function (jid, info) {
|
function onMucJoined(jid, info) {
|
||||||
Toolbar.updateRoomUrl(window.location.href);
|
Toolbar.updateRoomUrl(window.location.href);
|
||||||
document.getElementById('localNick').appendChild(
|
document.getElementById('localNick').appendChild(
|
||||||
document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
|
document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
|
||||||
|
@ -293,15 +319,14 @@ UI.onMucJoined = function (jid, info) {
|
||||||
|
|
||||||
// Show authenticate button if needed
|
// Show authenticate button if needed
|
||||||
Toolbar.showAuthenticateButton(
|
Toolbar.showAuthenticateButton(
|
||||||
Moderator.isExternalAuthEnabled() && !Moderator.isModerator());
|
xmpp.isExternalAuthEnabled() && !xmpp.isModerator());
|
||||||
|
|
||||||
var displayName = !config.displayJids
|
var displayName = !config.displayJids
|
||||||
? info.displayName : Strophe.getResourceFromJid(jid);
|
? info.displayName : Strophe.getResourceFromJid(jid);
|
||||||
|
|
||||||
if (displayName)
|
if (displayName)
|
||||||
$(document).trigger('displaynamechanged',
|
onDisplayNameChanged('localVideoContainer', displayName + ' (me)');
|
||||||
['localVideoContainer', displayName + ' (me)']);
|
}
|
||||||
};
|
|
||||||
|
|
||||||
UI.initEtherpad = function (name) {
|
UI.initEtherpad = function (name) {
|
||||||
Etherpad.init(name);
|
Etherpad.init(name);
|
||||||
|
@ -357,23 +382,19 @@ UI.toggleContactList = function () {
|
||||||
UI.onLocalRoleChange = function (jid, info, pres) {
|
UI.onLocalRoleChange = function (jid, info, pres) {
|
||||||
|
|
||||||
console.info("My role changed, new role: " + info.role);
|
console.info("My role changed, new role: " + info.role);
|
||||||
var isModerator = Moderator.isModerator();
|
var isModerator = xmpp.isModerator();
|
||||||
|
|
||||||
VideoLayout.showModeratorIndicator();
|
VideoLayout.showModeratorIndicator();
|
||||||
Toolbar.showAuthenticateButton(
|
Toolbar.showAuthenticateButton(
|
||||||
Moderator.isExternalAuthEnabled() && !isModerator);
|
xmpp.isExternalAuthEnabled() && !isModerator);
|
||||||
|
|
||||||
if (isModerator) {
|
if (isModerator) {
|
||||||
Toolbar.closeAuthenticationWindow();
|
Authentication.closeAuthenticationWindow();
|
||||||
messageHandler.notify(
|
messageHandler.notify(
|
||||||
'Me', 'connected', 'Moderator rights granted !');
|
'Me', 'connected', 'Moderator rights granted !');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.onDisposeConference = function (unload) {
|
|
||||||
Toolbar.showAuthenticateButton(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
UI.onModeratorStatusChanged = function (isModerator) {
|
UI.onModeratorStatusChanged = function (isModerator) {
|
||||||
|
|
||||||
Toolbar.showSipCallButton(isModerator);
|
Toolbar.showSipCallButton(isModerator);
|
||||||
|
@ -414,40 +435,11 @@ UI.onPasswordReqiured = function (callback) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.onAuthenticationRequired = function () {
|
UI.onAuthenticationRequired = function (intervalCallback) {
|
||||||
// This is the loop that will wait for the room to be created by
|
Authentication.openAuthenticationDialog(
|
||||||
// someone else. 'auth_required.moderator' will bring us back here.
|
roomName, intervalCallback, function () {
|
||||||
authRetryId = window.setTimeout(
|
|
||||||
function () {
|
|
||||||
Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
|
|
||||||
}, 5000);
|
|
||||||
// Show prompt only if it's not open
|
|
||||||
if (authDialog !== null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// extract room name from 'room@muc.server.net'
|
|
||||||
var room = roomName.substr(0, roomName.indexOf('@'));
|
|
||||||
|
|
||||||
authDialog = messageHandler.openDialog(
|
|
||||||
'Stop',
|
|
||||||
'Authentication is required to create room:<br/><b>' + room +
|
|
||||||
'</b></br> You can either authenticate to create the room or ' +
|
|
||||||
'just wait for someone else to do so.',
|
|
||||||
true,
|
|
||||||
{
|
|
||||||
Authenticate: 'authNow'
|
|
||||||
},
|
|
||||||
function (onSubmitEvent, submitValue) {
|
|
||||||
|
|
||||||
// Do not close the dialog yet
|
|
||||||
onSubmitEvent.preventDefault();
|
|
||||||
|
|
||||||
// Open login popup
|
|
||||||
if (submitValue === 'authNow') {
|
|
||||||
Toolbar.authenticateClicked();
|
Toolbar.authenticateClicked();
|
||||||
}
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.setRecordingButtonState = function (state) {
|
UI.setRecordingButtonState = function (state) {
|
||||||
|
@ -511,6 +503,8 @@ UI.showLocalAudioIndicator = function (mute) {
|
||||||
};
|
};
|
||||||
|
|
||||||
UI.generateRoomName = function() {
|
UI.generateRoomName = function() {
|
||||||
|
if(roomName)
|
||||||
|
return roomName;
|
||||||
var roomnode = null;
|
var roomnode = null;
|
||||||
var path = window.location.pathname;
|
var path = window.location.pathname;
|
||||||
|
|
||||||
|
@ -540,6 +534,7 @@ UI.generateRoomName = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
roomName = roomnode + '@' + config.hosts.muc;
|
roomName = roomnode + '@' + config.hosts.muc;
|
||||||
|
return roomName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -556,25 +551,146 @@ UI.dockToolbar = function (isDock) {
|
||||||
return ToolbarToggler.dockToolbar(isDock);
|
return ToolbarToggler.dockToolbar(isDock);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UI.getCreadentials = function () {
|
||||||
|
return {
|
||||||
|
bosh: document.getElementById('boshURL').value,
|
||||||
|
password: document.getElementById('password').value,
|
||||||
|
jid: document.getElementById('jid').value
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
UI.disableConnect = function () {
|
||||||
|
document.getElementById('connect').disabled = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
UI.showLoginPopup = function(callback)
|
||||||
|
{
|
||||||
|
console.log('password is required');
|
||||||
|
|
||||||
|
UI.messageHandler.openTwoButtonDialog(null,
|
||||||
|
'<h2>Password required</h2>' +
|
||||||
|
'<input id="passwordrequired.username" type="text" placeholder="user@domain.net" autofocus>' +
|
||||||
|
'<input id="passwordrequired.password" type="password" placeholder="user password">',
|
||||||
|
true,
|
||||||
|
"Ok",
|
||||||
|
function (e, v, m, f) {
|
||||||
|
if (v) {
|
||||||
|
var username = document.getElementById('passwordrequired.username');
|
||||||
|
var password = document.getElementById('passwordrequired.password');
|
||||||
|
|
||||||
|
if (username.value !== null && password.value != null) {
|
||||||
|
callback(username.value, password.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (event) {
|
||||||
|
document.getElementById('passwordrequired.username').focus();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.checkForNicknameAndJoin = function () {
|
||||||
|
|
||||||
|
Authentication.closeAuthenticationDialog();
|
||||||
|
Authentication.stopInterval();
|
||||||
|
|
||||||
|
var nick = null;
|
||||||
|
if (config.useNicks) {
|
||||||
|
nick = window.prompt('Your nickname (optional)');
|
||||||
|
}
|
||||||
|
xmpp.joinRooom(roomName, config.useNicks, nick);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function dump(elem, filename) {
|
function dump(elem, filename) {
|
||||||
elem = elem.parentNode;
|
elem = elem.parentNode;
|
||||||
elem.download = filename || 'meetlog.json';
|
elem.download = filename || 'meetlog.json';
|
||||||
elem.href = 'data:application/json;charset=utf-8,\n';
|
elem.href = 'data:application/json;charset=utf-8,\n';
|
||||||
var data = {};
|
var data = xmpp.populateData();
|
||||||
if (connection.jingle) {
|
|
||||||
data = connection.jingle.populateData();
|
|
||||||
}
|
|
||||||
var metadata = {};
|
var metadata = {};
|
||||||
metadata.time = new Date();
|
metadata.time = new Date();
|
||||||
metadata.url = window.location.href;
|
metadata.url = window.location.href;
|
||||||
metadata.ua = navigator.userAgent;
|
metadata.ua = navigator.userAgent;
|
||||||
if (connection.logger) {
|
var log = xmpp.getLogger();
|
||||||
metadata.xmpp = connection.logger.log;
|
if (log) {
|
||||||
|
metadata.xmpp = log;
|
||||||
}
|
}
|
||||||
data.metadata = metadata;
|
data.metadata = metadata;
|
||||||
elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
|
elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UI.getRoomName = function () {
|
||||||
|
return roomName;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutes/unmutes the local video.
|
||||||
|
*
|
||||||
|
* @param mute <tt>true</tt> to mute the local video; otherwise, <tt>false</tt>
|
||||||
|
* @param options an object which specifies optional arguments such as the
|
||||||
|
* <tt>boolean</tt> key <tt>byUser</tt> with default value <tt>true</tt> which
|
||||||
|
* specifies whether the method was initiated in response to a user command (in
|
||||||
|
* contrast to an automatic decision taken by the application logic)
|
||||||
|
*/
|
||||||
|
function setVideoMute(mute, options) {
|
||||||
|
xmpp.setVideoMute(
|
||||||
|
mute,
|
||||||
|
function (mute) {
|
||||||
|
var video = $('#video');
|
||||||
|
var communicativeClass = "icon-camera";
|
||||||
|
var muteClass = "icon-camera icon-camera-disabled";
|
||||||
|
|
||||||
|
if (mute) {
|
||||||
|
video.removeClass(communicativeClass);
|
||||||
|
video.addClass(muteClass);
|
||||||
|
} else {
|
||||||
|
video.removeClass(muteClass);
|
||||||
|
video.addClass(communicativeClass);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutes/unmutes the local video.
|
||||||
|
*/
|
||||||
|
UI.toggleVideo = function () {
|
||||||
|
UIUtil.buttonClick("#video", "icon-camera icon-camera-disabled");
|
||||||
|
|
||||||
|
setVideoMute(!RTC.localVideo.isMuted());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutes / unmutes audio for the local participant.
|
||||||
|
*/
|
||||||
|
UI.toggleAudio = function() {
|
||||||
|
UI.setAudioMuted(!RTC.localAudio.isMuted());
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets muted audio state for the local participant.
|
||||||
|
*/
|
||||||
|
UI.setAudioMuted = function (mute) {
|
||||||
|
|
||||||
|
if(!xmpp.setAudioMute(mute, function () {
|
||||||
|
UI.showLocalAudioIndicator(mute);
|
||||||
|
|
||||||
|
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
||||||
|
}))
|
||||||
|
{
|
||||||
|
// We still click the button.
|
||||||
|
UIUtil.buttonClick("#mute", "icon-microphone icon-mic-disabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
UI.onLastNChanged = function (oldValue, newValue) {
|
||||||
|
if (config.muteLocalVideoIfNotInLastN) {
|
||||||
|
setVideoMute(!newValue, { 'byUser': false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = UI;
|
module.exports = UI;
|
||||||
|
|
||||||
|
|
|
@ -87,10 +87,10 @@ var AudioLevels = (function(my) {
|
||||||
drawContext.drawImage(canvasCache, 0, 0);
|
drawContext.drawImage(canvasCache, 0, 0);
|
||||||
|
|
||||||
if(resourceJid === AudioLevels.LOCAL_LEVEL) {
|
if(resourceJid === AudioLevels.LOCAL_LEVEL) {
|
||||||
if(!connection.emuc.myroomjid) {
|
if(!xmpp.myJid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
resourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
resourceJid = xmpp.myResource();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(resourceJid === largeVideoResourceJid) {
|
if(resourceJid === largeVideoResourceJid) {
|
||||||
|
@ -221,8 +221,8 @@ var AudioLevels = (function(my) {
|
||||||
function getVideoSpanId(resourceJid) {
|
function getVideoSpanId(resourceJid) {
|
||||||
var videoSpanId = null;
|
var videoSpanId = null;
|
||||||
if (resourceJid === AudioLevels.LOCAL_LEVEL
|
if (resourceJid === AudioLevels.LOCAL_LEVEL
|
||||||
|| (connection.emuc.myroomjid && resourceJid
|
|| (xmpp.myResource() && resourceJid
|
||||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)))
|
=== xmpp.myResource()))
|
||||||
videoSpanId = 'localVideoContainer';
|
videoSpanId = 'localVideoContainer';
|
||||||
else
|
else
|
||||||
videoSpanId = 'participant_' + resourceJid;
|
videoSpanId = 'participant_' + resourceJid;
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* Initial "authentication required" dialog */
|
||||||
|
var authDialog = null;
|
||||||
|
/* Loop retry ID that wits for other user to create the room */
|
||||||
|
var authRetryId = null;
|
||||||
|
var authenticationWindow = null;
|
||||||
|
|
||||||
|
var Authentication = {
|
||||||
|
openAuthenticationDialog: function (roomName, intervalCallback, callback) {
|
||||||
|
// This is the loop that will wait for the room to be created by
|
||||||
|
// someone else. 'auth_required.moderator' will bring us back here.
|
||||||
|
authRetryId = window.setTimeout(intervalCallback , 5000);
|
||||||
|
// Show prompt only if it's not open
|
||||||
|
if (authDialog !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// extract room name from 'room@muc.server.net'
|
||||||
|
var room = roomName.substr(0, roomName.indexOf('@'));
|
||||||
|
|
||||||
|
authDialog = messageHandler.openDialog(
|
||||||
|
'Stop',
|
||||||
|
'Authentication is required to create room:<br/><b>' + room +
|
||||||
|
'</b></br> You can either authenticate to create the room or ' +
|
||||||
|
'just wait for someone else to do so.',
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
Authenticate: 'authNow'
|
||||||
|
},
|
||||||
|
function (onSubmitEvent, submitValue) {
|
||||||
|
|
||||||
|
// Do not close the dialog yet
|
||||||
|
onSubmitEvent.preventDefault();
|
||||||
|
|
||||||
|
// Open login popup
|
||||||
|
if (submitValue === 'authNow') {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
closeAuthenticationWindow:function () {
|
||||||
|
if (authenticationWindow) {
|
||||||
|
authenticationWindow.close();
|
||||||
|
authenticationWindow = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
focusAuthenticationWindow: function () {
|
||||||
|
// If auth window exists just bring it to the front
|
||||||
|
if (authenticationWindow) {
|
||||||
|
authenticationWindow.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeAuthenticationDialog: function () {
|
||||||
|
// Close authentication dialog if opened
|
||||||
|
if (authDialog) {
|
||||||
|
UI.messageHandler.closeDialog();
|
||||||
|
authDialog = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createAuthenticationWindow: function (callback, url) {
|
||||||
|
authenticationWindow = messageHandler.openCenteredPopup(
|
||||||
|
url, 910, 660,
|
||||||
|
// On closed
|
||||||
|
function () {
|
||||||
|
// Close authentication dialog if opened
|
||||||
|
if (authDialog) {
|
||||||
|
messageHandler.closeDialog();
|
||||||
|
authDialog = null;
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
authenticationWindow = null;
|
||||||
|
});
|
||||||
|
return authenticationWindow;
|
||||||
|
},
|
||||||
|
stopInterval: function () {
|
||||||
|
// Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
|
||||||
|
if (authRetryId) {
|
||||||
|
window.clearTimeout(authRetryId);
|
||||||
|
authRetryId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = Authentication;
|
|
@ -12,7 +12,7 @@ function setVisibility(selector, show) {
|
||||||
function isUserMuted(jid) {
|
function isUserMuted(jid) {
|
||||||
// XXX(gp) we may want to rename this method to something like
|
// XXX(gp) we may want to rename this method to something like
|
||||||
// isUserStreaming, for example.
|
// isUserStreaming, for example.
|
||||||
if (jid && jid != connection.emuc.myroomjid) {
|
if (jid && jid != xmpp.myJid()) {
|
||||||
var resource = Strophe.getResourceFromJid(jid);
|
var resource = Strophe.getResourceFromJid(jid);
|
||||||
if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
|
if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -26,7 +26,7 @@ function isUserMuted(jid) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getGravatarUrl(id, size) {
|
function getGravatarUrl(id, size) {
|
||||||
if(id === connection.emuc.myroomjid || !id) {
|
if(id === xmpp.myJid() || !id) {
|
||||||
id = Settings.getSettings().uid;
|
id = Settings.getSettings().uid;
|
||||||
}
|
}
|
||||||
return 'https://www.gravatar.com/avatar/' +
|
return 'https://www.gravatar.com/avatar/' +
|
||||||
|
@ -57,7 +57,7 @@ var Avatar = {
|
||||||
|
|
||||||
// set the avatar in the settings menu if it is local user and get the
|
// set the avatar in the settings menu if it is local user and get the
|
||||||
// local video container
|
// local video container
|
||||||
if (jid === connection.emuc.myroomjid) {
|
if (jid === xmpp.myJid()) {
|
||||||
$('#avatar').get(0).src = thumbUrl;
|
$('#avatar').get(0).src = thumbUrl;
|
||||||
thumbnail = $('#localVideoContainer');
|
thumbnail = $('#localVideoContainer');
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ var Avatar = {
|
||||||
var video = $('#participant_' + resourceJid + '>video');
|
var video = $('#participant_' + resourceJid + '>video');
|
||||||
var avatar = $('#avatar_' + resourceJid);
|
var avatar = $('#avatar_' + resourceJid);
|
||||||
|
|
||||||
if (jid === connection.emuc.myroomjid) {
|
if (jid === xmpp.myJid()) {
|
||||||
video = $('#localVideoWrapper>video');
|
video = $('#localVideoWrapper>video');
|
||||||
}
|
}
|
||||||
if (show === undefined || show === null) {
|
if (show === undefined || show === null) {
|
||||||
|
@ -130,7 +130,7 @@ var Avatar = {
|
||||||
*/
|
*/
|
||||||
updateActiveSpeakerAvatarSrc: function (jid) {
|
updateActiveSpeakerAvatarSrc: function (jid) {
|
||||||
if (!jid) {
|
if (!jid) {
|
||||||
jid = connection.emuc.findJidFromResource(
|
jid = xmpp.findJidFromResource(
|
||||||
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
|
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
|
||||||
}
|
}
|
||||||
var avatar = $("#activeSpeakerAvatar")[0];
|
var avatar = $("#activeSpeakerAvatar")[0];
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global $, config, connection, dockToolbar, Moderator,
|
/* global $, config, dockToolbar,
|
||||||
setLargeVideoVisible, Util */
|
setLargeVideoVisible, Util */
|
||||||
|
|
||||||
var VideoLayout = require("../videolayout/VideoLayout");
|
var VideoLayout = require("../videolayout/VideoLayout");
|
||||||
|
@ -30,8 +30,7 @@ function resize() {
|
||||||
* Shares the Etherpad name with other participants.
|
* Shares the Etherpad name with other participants.
|
||||||
*/
|
*/
|
||||||
function shareEtherpad() {
|
function shareEtherpad() {
|
||||||
connection.emuc.addEtherpadToPresence(etherpadName);
|
xmpp.addToPresence("etherpad", etherpadName);
|
||||||
connection.emuc.sendPresence();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -30,7 +30,7 @@ var Prezi = {
|
||||||
* to load.
|
* to load.
|
||||||
*/
|
*/
|
||||||
openPreziDialog: function() {
|
openPreziDialog: function() {
|
||||||
var myprezi = connection.emuc.getPrezi(connection.emuc.myroomjid);
|
var myprezi = xmpp.getPrezi();
|
||||||
if (myprezi) {
|
if (myprezi) {
|
||||||
messageHandler.openTwoButtonDialog("Remove Prezi",
|
messageHandler.openTwoButtonDialog("Remove Prezi",
|
||||||
"Are you sure you would like to remove your Prezi?",
|
"Are you sure you would like to remove your Prezi?",
|
||||||
|
@ -38,8 +38,7 @@ var Prezi = {
|
||||||
"Remove",
|
"Remove",
|
||||||
function(e,v,m,f) {
|
function(e,v,m,f) {
|
||||||
if(v) {
|
if(v) {
|
||||||
connection.emuc.removePreziFromPresence();
|
xmpp.removePreziFromPresence();
|
||||||
connection.emuc.sendPresence();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -91,9 +90,7 @@ var Prezi = {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
connection.emuc
|
xmpp.addToPresence("prezi", urlValue);
|
||||||
.addPreziToPresence(urlValue, 0);
|
|
||||||
connection.emuc.sendPresence();
|
|
||||||
$.prompt.close();
|
$.prompt.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,7 +148,7 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
|
||||||
VideoLayout.resizeThumbnails();
|
VideoLayout.resizeThumbnails();
|
||||||
|
|
||||||
var controlsEnabled = false;
|
var controlsEnabled = false;
|
||||||
if (jid === connection.emuc.myroomjid)
|
if (jid === xmpp.myJid())
|
||||||
controlsEnabled = true;
|
controlsEnabled = true;
|
||||||
|
|
||||||
setPresentationVisible(true);
|
setPresentationVisible(true);
|
||||||
|
@ -191,15 +188,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
|
||||||
preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
|
preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
|
||||||
console.log("prezi status", event.value);
|
console.log("prezi status", event.value);
|
||||||
if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
|
if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
|
||||||
if (jid != connection.emuc.myroomjid)
|
if (jid != xmpp.myJid())
|
||||||
preziPlayer.flyToStep(currentSlide);
|
preziPlayer.flyToStep(currentSlide);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
|
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
|
||||||
console.log("event value", event.value);
|
console.log("event value", event.value);
|
||||||
connection.emuc.addCurrentSlideToPresence(event.value);
|
xmpp.addToPresence("preziSlide", event.value);
|
||||||
connection.emuc.sendPresence();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#" + elementId).css( 'background-image',
|
$("#" + elementId).css( 'background-image',
|
||||||
|
|
|
@ -4,6 +4,7 @@ var Settings = require("./settings/Settings");
|
||||||
var SettingsMenu = require("./settings/SettingsMenu");
|
var SettingsMenu = require("./settings/SettingsMenu");
|
||||||
var VideoLayout = require("../videolayout/VideoLayout");
|
var VideoLayout = require("../videolayout/VideoLayout");
|
||||||
var ToolbarToggler = require("../toolbars/ToolbarToggler");
|
var ToolbarToggler = require("../toolbars/ToolbarToggler");
|
||||||
|
var UIUtil = require("../util/UIUtil");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggler for the chat, contact list, settings menu, etc..
|
* Toggler for the chat, contact list, settings menu, etc..
|
||||||
|
@ -110,7 +111,7 @@ var PanelToggler = (function(my) {
|
||||||
* @param onClose function to be called if the window is going to be closed
|
* @param onClose function to be called if the window is going to be closed
|
||||||
*/
|
*/
|
||||||
var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
|
var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
|
||||||
buttonClick(buttons[selector], "active");
|
UIUtil.buttonClick(buttons[selector], "active");
|
||||||
|
|
||||||
if (object.isVisible()) {
|
if (object.isVisible()) {
|
||||||
$("#toast-container").animate({
|
$("#toast-container").animate({
|
||||||
|
@ -140,7 +141,7 @@ var PanelToggler = (function(my) {
|
||||||
|
|
||||||
if(currentlyOpen) {
|
if(currentlyOpen) {
|
||||||
var current = $(currentlyOpen);
|
var current = $(currentlyOpen);
|
||||||
buttonClick(buttons[currentlyOpen], "active");
|
UIUtil.buttonClick(buttons[currentlyOpen], "active");
|
||||||
current.css('z-index', 4);
|
current.css('z-index', 4);
|
||||||
setTimeout(function () {
|
setTimeout(function () {
|
||||||
current.css('display', 'none');
|
current.css('display', 'none');
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global $, Util, connection, nickname:true, showToolbar */
|
/* global $, Util, nickname:true, showToolbar */
|
||||||
var Replacement = require("./Replacement");
|
var Replacement = require("./Replacement");
|
||||||
var CommandsProcessor = require("./Commands");
|
var CommandsProcessor = require("./Commands");
|
||||||
var ToolbarToggler = require("../../toolbars/ToolbarToggler");
|
var ToolbarToggler = require("../../toolbars/ToolbarToggler");
|
||||||
|
@ -184,8 +184,7 @@ var Chat = (function (my) {
|
||||||
nickname = val;
|
nickname = val;
|
||||||
window.localStorage.displayname = nickname;
|
window.localStorage.displayname = nickname;
|
||||||
|
|
||||||
connection.emuc.addDisplayNameToPresence(nickname);
|
xmpp.addToPresence("displayName", nickname);
|
||||||
connection.emuc.sendPresence();
|
|
||||||
|
|
||||||
Chat.setChatConversationMode(true);
|
Chat.setChatConversationMode(true);
|
||||||
|
|
||||||
|
@ -208,7 +207,7 @@ var Chat = (function (my) {
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var message = Util.escapeHtml(value);
|
var message = Util.escapeHtml(value);
|
||||||
connection.emuc.sendMessage(message, nickname);
|
xmpp.sendChatMessage(message, nickname);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -234,7 +233,7 @@ var Chat = (function (my) {
|
||||||
my.updateChatConversation = function (from, displayName, message) {
|
my.updateChatConversation = function (from, displayName, message) {
|
||||||
var divClassName = '';
|
var divClassName = '';
|
||||||
|
|
||||||
if (connection.emuc.myroomjid === from) {
|
if (xmpp.myJid() === from) {
|
||||||
divClassName = "localuser";
|
divClassName = "localuser";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
|
@ -32,7 +32,7 @@ function getCommand(message)
|
||||||
function processTopic(commandArguments)
|
function processTopic(commandArguments)
|
||||||
{
|
{
|
||||||
var topic = Util.escapeHtml(commandArguments);
|
var topic = Util.escapeHtml(commandArguments);
|
||||||
connection.emuc.setSubject(topic);
|
xmpp.setSubject(topic);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -46,23 +46,6 @@ function createDisplayNameParagraph(displayName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates that the display name has changed.
|
|
||||||
*/
|
|
||||||
$(document).bind( 'displaynamechanged',
|
|
||||||
function (event, peerJid, displayName) {
|
|
||||||
if (peerJid === 'localVideoContainer')
|
|
||||||
peerJid = connection.emuc.myroomjid;
|
|
||||||
|
|
||||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
|
||||||
|
|
||||||
var contactName = $('#contactlist #' + resourceJid + '>p');
|
|
||||||
|
|
||||||
if (contactName && displayName && displayName.length > 0)
|
|
||||||
contactName.html(displayName);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function stopGlowing(glower) {
|
function stopGlowing(glower) {
|
||||||
window.clearInterval(notificationInterval);
|
window.clearInterval(notificationInterval);
|
||||||
notificationInterval = false;
|
notificationInterval = false;
|
||||||
|
@ -127,7 +110,7 @@ var ContactList = {
|
||||||
|
|
||||||
var clElement = contactlist.get(0);
|
var clElement = contactlist.get(0);
|
||||||
|
|
||||||
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
|
if (resourceJid === xmpp.myResource()
|
||||||
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
|
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
|
||||||
clElement.insertBefore(newContact,
|
clElement.insertBefore(newContact,
|
||||||
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
|
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
|
||||||
|
@ -182,6 +165,18 @@ var ContactList = {
|
||||||
} else {
|
} else {
|
||||||
contact.removeClass('clickable');
|
contact.removeClass('clickable');
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDisplayNameChange: function (peerJid, displayName) {
|
||||||
|
if (peerJid === 'localVideoContainer')
|
||||||
|
peerJid = xmpp.myJid();
|
||||||
|
|
||||||
|
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||||
|
|
||||||
|
var contactName = $('#contactlist #' + resourceJid + '>p');
|
||||||
|
|
||||||
|
if (contactName && displayName && displayName.length > 0)
|
||||||
|
contactName.html(displayName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,16 +10,15 @@ var SettingsMenu = {
|
||||||
|
|
||||||
if(newDisplayName) {
|
if(newDisplayName) {
|
||||||
var displayName = Settings.setDisplayName(newDisplayName);
|
var displayName = Settings.setDisplayName(newDisplayName);
|
||||||
connection.emuc.addDisplayNameToPresence(displayName);
|
xmpp.addToPresence("displayName", displayName, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
connection.emuc.addEmailToPresence(newEmail);
|
xmpp.addToPresence("email", newEmail);
|
||||||
var email = Settings.setEmail(newEmail);
|
var email = Settings.setEmail(newEmail);
|
||||||
|
|
||||||
|
|
||||||
connection.emuc.sendPresence();
|
Avatar.setUserAvatar(xmpp.myJid(), email);
|
||||||
Avatar.setUserAvatar(connection.emuc.myroomjid, email);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
isVisible: function() {
|
isVisible: function() {
|
||||||
|
@ -29,14 +28,15 @@ var SettingsMenu = {
|
||||||
setDisplayName: function(newDisplayName) {
|
setDisplayName: function(newDisplayName) {
|
||||||
var displayName = Settings.setDisplayName(newDisplayName);
|
var displayName = Settings.setDisplayName(newDisplayName);
|
||||||
$('#setDisplayName').get(0).value = displayName;
|
$('#setDisplayName').get(0).value = displayName;
|
||||||
|
},
|
||||||
|
|
||||||
|
onDisplayNameChange: function(peerJid, newDisplayName) {
|
||||||
|
if(peerJid === 'localVideoContainer' ||
|
||||||
|
peerJid === xmpp.myJid()) {
|
||||||
|
this.setDisplayName(newDisplayName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$(document).bind('displaynamechanged', function(event, peerJid, newDisplayName) {
|
|
||||||
if(peerJid === 'localVideoContainer' ||
|
|
||||||
peerJid === connection.emuc.myroomjid) {
|
|
||||||
SettingsMenu.setDisplayName(newDisplayName);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
module.exports = SettingsMenu;
|
module.exports = SettingsMenu;
|
|
@ -1,22 +1,24 @@
|
||||||
/* global $, buttonClick, config, lockRoom, Moderator, roomName,
|
/* global $, buttonClick, config, lockRoom,
|
||||||
setSharedKey, sharedKey, Util */
|
setSharedKey, Util */
|
||||||
var messageHandler = require("../util/MessageHandler");
|
var messageHandler = require("../util/MessageHandler");
|
||||||
var BottomToolbar = require("./BottomToolbar");
|
var BottomToolbar = require("./BottomToolbar");
|
||||||
var Prezi = require("../prezi/Prezi");
|
var Prezi = require("../prezi/Prezi");
|
||||||
var Etherpad = require("../etherpad/Etherpad");
|
var Etherpad = require("../etherpad/Etherpad");
|
||||||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||||
|
var Authentication = require("../authentication/Authentication");
|
||||||
|
var UIUtil = require("../util/UIUtil");
|
||||||
|
|
||||||
var roomUrl = null;
|
var roomUrl = null;
|
||||||
var sharedKey = '';
|
var sharedKey = '';
|
||||||
var authenticationWindow = null;
|
var UI = null;
|
||||||
|
|
||||||
var buttonHandlers =
|
var buttonHandlers =
|
||||||
{
|
{
|
||||||
"toolbar_button_mute": function () {
|
"toolbar_button_mute": function () {
|
||||||
return toggleAudio();
|
return UI.toggleAudio();
|
||||||
},
|
},
|
||||||
"toolbar_button_camera": function () {
|
"toolbar_button_camera": function () {
|
||||||
return toggleVideo();
|
return UI.toggleVideo();
|
||||||
},
|
},
|
||||||
"toolbar_button_authentication": function () {
|
"toolbar_button_authentication": function () {
|
||||||
return Toolbar.authenticateClicked();
|
return Toolbar.authenticateClicked();
|
||||||
|
@ -44,7 +46,7 @@ var buttonHandlers =
|
||||||
},
|
},
|
||||||
"toolbar_button_fullScreen": function()
|
"toolbar_button_fullScreen": function()
|
||||||
{
|
{
|
||||||
buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
|
UIUtil.buttonClick("#fullScreen", "icon-full-screen icon-exit-full-screen");
|
||||||
return Toolbar.toggleFullScreen();
|
return Toolbar.toggleFullScreen();
|
||||||
},
|
},
|
||||||
"toolbar_button_sip": function () {
|
"toolbar_button_sip": function () {
|
||||||
|
@ -59,9 +61,7 @@ var buttonHandlers =
|
||||||
};
|
};
|
||||||
|
|
||||||
function hangup() {
|
function hangup() {
|
||||||
disposeConference();
|
xmpp.disposeConference();
|
||||||
sessionTerminated = true;
|
|
||||||
connection.emuc.doLeave();
|
|
||||||
if(config.enableWelcomePage)
|
if(config.enableWelcomePage)
|
||||||
{
|
{
|
||||||
setTimeout(function()
|
setTimeout(function()
|
||||||
|
@ -90,7 +90,29 @@ function hangup() {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function toggleRecording() {
|
function toggleRecording() {
|
||||||
Recording.toggleRecording();
|
xmpp.toggleRecording(function (callback) {
|
||||||
|
UI.messageHandler.openTwoButtonDialog(null,
|
||||||
|
'<h2>Enter recording token</h2>' +
|
||||||
|
'<input id="recordingToken" type="text" ' +
|
||||||
|
'placeholder="token" autofocus>',
|
||||||
|
false,
|
||||||
|
"Save",
|
||||||
|
function (e, v, m, f) {
|
||||||
|
if (v) {
|
||||||
|
var token = document.getElementById('recordingToken');
|
||||||
|
|
||||||
|
if (token.value) {
|
||||||
|
callback(Util.escapeHtml(token.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function (event) {
|
||||||
|
document.getElementById('recordingToken').focus();
|
||||||
|
},
|
||||||
|
function () {
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}, Toolbar.setRecordingButtonState, Toolbar.setRecordingButtonState);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -101,7 +123,7 @@ function lockRoom(lock) {
|
||||||
if (lock)
|
if (lock)
|
||||||
currentSharedKey = sharedKey;
|
currentSharedKey = sharedKey;
|
||||||
|
|
||||||
connection.emuc.lockRoom(currentSharedKey, function (res) {
|
xmpp.lockRoom(currentSharedKey, function (res) {
|
||||||
// password is required
|
// password is required
|
||||||
if (sharedKey)
|
if (sharedKey)
|
||||||
{
|
{
|
||||||
|
@ -183,9 +205,8 @@ function callSipButtonClicked()
|
||||||
if (v) {
|
if (v) {
|
||||||
var numberInput = document.getElementById('sipNumber');
|
var numberInput = document.getElementById('sipNumber');
|
||||||
if (numberInput.value) {
|
if (numberInput.value) {
|
||||||
connection.rayo.dial(
|
xmpp.dial(numberInput.value, 'fromnumber',
|
||||||
numberInput.value, 'fromnumber',
|
UI.getRoomName(), sharedKey);
|
||||||
roomName, sharedKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -197,9 +218,10 @@ function callSipButtonClicked()
|
||||||
|
|
||||||
var Toolbar = (function (my) {
|
var Toolbar = (function (my) {
|
||||||
|
|
||||||
my.init = function () {
|
my.init = function (ui) {
|
||||||
for(var k in buttonHandlers)
|
for(var k in buttonHandlers)
|
||||||
$("#" + k).click(buttonHandlers[k]);
|
$("#" + k).click(buttonHandlers[k]);
|
||||||
|
UI = ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -210,35 +232,15 @@ var Toolbar = (function (my) {
|
||||||
sharedKey = sKey;
|
sharedKey = sKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
my.closeAuthenticationWindow = function () {
|
|
||||||
if (authenticationWindow) {
|
|
||||||
authenticationWindow.close();
|
|
||||||
authenticationWindow = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
my.authenticateClicked = function () {
|
my.authenticateClicked = function () {
|
||||||
// If auth window exists just bring it to the front
|
Authentication.focusAuthenticationWindow();
|
||||||
if (authenticationWindow) {
|
|
||||||
authenticationWindow.focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Get authentication URL
|
// Get authentication URL
|
||||||
Moderator.getAuthUrl(function (url) {
|
xmpp.getAuthUrl(UI.getRoomName(), function (url) {
|
||||||
// Open popup with authentication URL
|
// Open popup with authentication URL
|
||||||
authenticationWindow = messageHandler.openCenteredPopup(
|
var authenticationWindow = Authentication.createAuthenticationWindow(function () {
|
||||||
url, 910, 660,
|
|
||||||
// On closed
|
|
||||||
function () {
|
|
||||||
// Close authentication dialog if opened
|
|
||||||
if (authDialog) {
|
|
||||||
messageHandler.closeDialog();
|
|
||||||
authDialog = null;
|
|
||||||
}
|
|
||||||
// On popup closed - retry room allocation
|
// On popup closed - retry room allocation
|
||||||
Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
|
xmpp.allocateConferenceFocus(UI.getRoomName(), UI.checkForNicknameAndJoin);
|
||||||
authenticationWindow = null;
|
}, url);
|
||||||
});
|
|
||||||
if (!authenticationWindow) {
|
if (!authenticationWindow) {
|
||||||
Toolbar.showAuthenticateButton(true);
|
Toolbar.showAuthenticateButton(true);
|
||||||
messageHandler.openMessageDialog(
|
messageHandler.openMessageDialog(
|
||||||
|
@ -279,7 +281,7 @@ var Toolbar = (function (my) {
|
||||||
*/
|
*/
|
||||||
my.openLockDialog = function () {
|
my.openLockDialog = function () {
|
||||||
// Only the focus is able to set a shared key.
|
// Only the focus is able to set a shared key.
|
||||||
if (!Moderator.isModerator()) {
|
if (!xmpp.isModerator()) {
|
||||||
if (sharedKey) {
|
if (sharedKey) {
|
||||||
messageHandler.openMessageDialog(null,
|
messageHandler.openMessageDialog(null,
|
||||||
"This conversation is currently protected by" +
|
"This conversation is currently protected by" +
|
||||||
|
@ -436,14 +438,14 @@ var Toolbar = (function (my) {
|
||||||
*/
|
*/
|
||||||
my.unlockLockButton = function () {
|
my.unlockLockButton = function () {
|
||||||
if ($("#lockIcon").hasClass("icon-security-locked"))
|
if ($("#lockIcon").hasClass("icon-security-locked"))
|
||||||
buttonClick("#lockIcon", "icon-security icon-security-locked");
|
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Updates the lock button state to locked.
|
* Updates the lock button state to locked.
|
||||||
*/
|
*/
|
||||||
my.lockLockButton = function () {
|
my.lockLockButton = function () {
|
||||||
if ($("#lockIcon").hasClass("icon-security"))
|
if ($("#lockIcon").hasClass("icon-security"))
|
||||||
buttonClick("#lockIcon", "icon-security icon-security-locked");
|
UIUtil.buttonClick("#lockIcon", "icon-security icon-security-locked");
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -486,7 +488,7 @@ var Toolbar = (function (my) {
|
||||||
|
|
||||||
// Shows or hides SIP calls button
|
// Shows or hides SIP calls button
|
||||||
my.showSipCallButton = function (show) {
|
my.showSipCallButton = function (show) {
|
||||||
if (Moderator.isSipGatewayEnabled() && show) {
|
if (xmpp.isSipGatewayEnabled() && show) {
|
||||||
$('#sipCallButton').css({display: "inline"});
|
$('#sipCallButton').css({display: "inline"});
|
||||||
} else {
|
} else {
|
||||||
$('#sipCallButton').css({display: "none"});
|
$('#sipCallButton').css({display: "none"});
|
||||||
|
|
|
@ -67,7 +67,7 @@ var ToolbarToggler = {
|
||||||
toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Moderator.isModerator())
|
if (xmpp.isModerator())
|
||||||
{
|
{
|
||||||
// TODO: Enable settings functionality.
|
// TODO: Enable settings functionality.
|
||||||
// Need to uncomment the settings button in index.html.
|
// Need to uncomment the settings button in index.html.
|
||||||
|
|
|
@ -11,6 +11,13 @@ module.exports = {
|
||||||
= PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
|
= PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
|
||||||
|
|
||||||
return window.innerWidth - rightPanelWidth;
|
return window.innerWidth - rightPanelWidth;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Changes the style class of the element given by id.
|
||||||
|
*/
|
||||||
|
buttonClick: function(id, classname) {
|
||||||
|
$(id).toggleClass(classname); // add the class to the clicked element
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
|
@ -16,8 +16,99 @@ var largeVideoState = {
|
||||||
newSrc: ''
|
newSrc: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if we have muted our audio before the conference has started.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
var preMuted = false;
|
||||||
|
|
||||||
|
var mutedAudios = {};
|
||||||
|
|
||||||
|
var flipXLocalVideo = true;
|
||||||
|
var currentVideoWidth = null;
|
||||||
|
var currentVideoHeight = null;
|
||||||
|
|
||||||
|
var localVideoSrc = null;
|
||||||
|
|
||||||
var defaultLocalDisplayName = "Me";
|
var defaultLocalDisplayName = "Me";
|
||||||
|
|
||||||
|
function videoactive( videoelem) {
|
||||||
|
if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
|
||||||
|
// ignore mixedmslabela0 and v0
|
||||||
|
|
||||||
|
videoelem.show();
|
||||||
|
VideoLayout.resizeThumbnails();
|
||||||
|
|
||||||
|
var videoParent = videoelem.parent();
|
||||||
|
var parentResourceJid = null;
|
||||||
|
if (videoParent)
|
||||||
|
parentResourceJid
|
||||||
|
= VideoLayout.getPeerContainerResourceJid(videoParent[0]);
|
||||||
|
|
||||||
|
// Update the large video to the last added video only if there's no
|
||||||
|
// current dominant, focused speaker or prezi playing or update it to
|
||||||
|
// the current dominant speaker.
|
||||||
|
if ((!focusedVideoInfo &&
|
||||||
|
!VideoLayout.getDominantSpeakerResourceJid() &&
|
||||||
|
!require("../prezi/Prezi").isPresentationVisible()) ||
|
||||||
|
(parentResourceJid &&
|
||||||
|
VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) {
|
||||||
|
VideoLayout.updateLargeVideo(
|
||||||
|
RTC.getVideoSrc(videoelem[0]),
|
||||||
|
1,
|
||||||
|
parentResourceJid);
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoLayout.showModeratorIndicator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function waitForRemoteVideo(selector, ssrc, stream, jid) {
|
||||||
|
// XXX(gp) so, every call to this function is *always* preceded by a call
|
||||||
|
// to the RTC.attachMediaStream() function but that call is *not* followed
|
||||||
|
// by an update to the videoSrcToSsrc map!
|
||||||
|
//
|
||||||
|
// The above way of doing things results in video SRCs that don't correspond
|
||||||
|
// to any SSRC for a short period of time (to be more precise, for as long
|
||||||
|
// the waitForRemoteVideo takes to complete). This causes problems (see
|
||||||
|
// bellow).
|
||||||
|
//
|
||||||
|
// I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
|
||||||
|
// a second time in here and only then update the videoSrcToSsrc map? Why
|
||||||
|
// not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
|
||||||
|
// is called the first time? I actually do that in the lastN changed event
|
||||||
|
// handler because the "orphan" video SRC is causing troubles there. The
|
||||||
|
// purpose of this method would then be to fire the "videoactive.jingle".
|
||||||
|
//
|
||||||
|
// Food for though I guess :-)
|
||||||
|
|
||||||
|
if (selector.removed || !selector.parent().is(":visible")) {
|
||||||
|
console.warn("Media removed before had started", selector);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stream.id === 'mixedmslabel') return;
|
||||||
|
|
||||||
|
if (selector[0].currentTime > 0) {
|
||||||
|
var videoStream = simulcast.getReceivingVideoStream(stream);
|
||||||
|
RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
|
||||||
|
|
||||||
|
// FIXME: add a class that will associate peer Jid, video.src, it's ssrc and video type
|
||||||
|
// in order to get rid of too many maps
|
||||||
|
if (ssrc && jid) {
|
||||||
|
jid2Ssrc[Strophe.getResourceFromJid(jid)] = ssrc;
|
||||||
|
} else {
|
||||||
|
console.warn("No ssrc given for jid", jid);
|
||||||
|
}
|
||||||
|
|
||||||
|
videoactive(selector);
|
||||||
|
} else {
|
||||||
|
setTimeout(function () {
|
||||||
|
waitForRemoteVideo(selector, ssrc, stream, jid);
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an array of the video horizontal and vertical indents,
|
* Returns an array of the video horizontal and vertical indents,
|
||||||
* so that if fits its parent.
|
* so that if fits its parent.
|
||||||
|
@ -194,7 +285,7 @@ function getParticipantContainer(resourceJid)
|
||||||
if (!resourceJid)
|
if (!resourceJid)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
if (resourceJid === xmpp.myResource())
|
||||||
return $("#localVideoContainer");
|
return $("#localVideoContainer");
|
||||||
else
|
else
|
||||||
return $("#participant_" + resourceJid);
|
return $("#participant_" + resourceJid);
|
||||||
|
@ -270,7 +361,8 @@ function addRemoteVideoMenu(jid, parentElement) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
var isMute = mutedAudios[jid] == true;
|
var isMute = mutedAudios[jid] == true;
|
||||||
connection.moderate.setMute(jid, !isMute);
|
xmpp.setMute(jid, !isMute);
|
||||||
|
|
||||||
popupmenuElement.setAttribute('style', 'display:none;');
|
popupmenuElement.setAttribute('style', 'display:none;');
|
||||||
|
|
||||||
if (isMute) {
|
if (isMute) {
|
||||||
|
@ -292,7 +384,7 @@ function addRemoteVideoMenu(jid, parentElement) {
|
||||||
var ejectLinkItem = document.createElement('a');
|
var ejectLinkItem = document.createElement('a');
|
||||||
ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
|
ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
|
||||||
ejectLinkItem.onclick = function(){
|
ejectLinkItem.onclick = function(){
|
||||||
connection.moderate.eject(jid);
|
xmpp.eject(jid);
|
||||||
popupmenuElement.setAttribute('style', 'display:none;');
|
popupmenuElement.setAttribute('style', 'display:none;');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -400,6 +492,43 @@ function createModeratorIndicatorElement(parentElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if video identified by given src is desktop stream.
|
||||||
|
* @param videoSrc eg.
|
||||||
|
* blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function isVideoSrcDesktop(jid) {
|
||||||
|
// FIXME: fix this mapping mess...
|
||||||
|
// figure out if large video is desktop stream or just a camera
|
||||||
|
|
||||||
|
if(!jid)
|
||||||
|
return false;
|
||||||
|
var isDesktop = false;
|
||||||
|
if (xmpp.myJid() &&
|
||||||
|
xmpp.myResource() === jid) {
|
||||||
|
// local video
|
||||||
|
isDesktop = desktopsharing.isUsingScreenStream();
|
||||||
|
} else {
|
||||||
|
// Do we have associations...
|
||||||
|
var videoSsrc = jid2Ssrc[jid];
|
||||||
|
if (videoSsrc) {
|
||||||
|
var videoType = ssrc2videoType[videoSsrc];
|
||||||
|
if (videoType) {
|
||||||
|
// Finally there...
|
||||||
|
isDesktop = videoType === 'screen';
|
||||||
|
} else {
|
||||||
|
console.error("No video type for ssrc: " + videoSsrc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("No ssrc for jid: " + jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return isDesktop;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var VideoLayout = (function (my) {
|
var VideoLayout = (function (my) {
|
||||||
my.connectionIndicators = {};
|
my.connectionIndicators = {};
|
||||||
|
|
||||||
|
@ -407,6 +536,16 @@ var VideoLayout = (function (my) {
|
||||||
my.getVideoSize = getCameraVideoSize;
|
my.getVideoSize = getCameraVideoSize;
|
||||||
my.getVideoPosition = getCameraVideoPosition;
|
my.getVideoPosition = getCameraVideoPosition;
|
||||||
|
|
||||||
|
my.init = function () {
|
||||||
|
// Listen for large video size updates
|
||||||
|
document.getElementById('largeVideo')
|
||||||
|
.addEventListener('loadedmetadata', function (e) {
|
||||||
|
currentVideoWidth = this.videoWidth;
|
||||||
|
currentVideoHeight = this.videoHeight;
|
||||||
|
VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
my.isInLastN = function(resource) {
|
my.isInLastN = function(resource) {
|
||||||
return lastNCount < 0 // lastN is disabled, return true
|
return lastNCount < 0 // lastN is disabled, return true
|
||||||
|| (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
|
|| (lastNCount > 0 && lastNEndpointsCache.length == 0) // lastNEndpoints cache not built yet, return true
|
||||||
|
@ -422,7 +561,10 @@ var VideoLayout = (function (my) {
|
||||||
document.getElementById('localAudio').autoplay = true;
|
document.getElementById('localAudio').autoplay = true;
|
||||||
document.getElementById('localAudio').volume = 0;
|
document.getElementById('localAudio').volume = 0;
|
||||||
if (preMuted) {
|
if (preMuted) {
|
||||||
setAudioMuted(true);
|
if(!UI.setAudioMuted(true))
|
||||||
|
{
|
||||||
|
preMuted = mute;
|
||||||
|
}
|
||||||
preMuted = false;
|
preMuted = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -459,14 +601,14 @@ var VideoLayout = (function (my) {
|
||||||
VideoLayout.handleVideoThumbClicked(
|
VideoLayout.handleVideoThumbClicked(
|
||||||
RTC.getVideoSrc(localVideo),
|
RTC.getVideoSrc(localVideo),
|
||||||
false,
|
false,
|
||||||
Strophe.getResourceFromJid(connection.emuc.myroomjid));
|
xmpp.myResource());
|
||||||
});
|
});
|
||||||
$('#localVideoContainer').click(function (event) {
|
$('#localVideoContainer').click(function (event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
VideoLayout.handleVideoThumbClicked(
|
VideoLayout.handleVideoThumbClicked(
|
||||||
RTC.getVideoSrc(localVideo),
|
RTC.getVideoSrc(localVideo),
|
||||||
false,
|
false,
|
||||||
Strophe.getResourceFromJid(connection.emuc.myroomjid));
|
xmpp.myResource());
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add hover handler
|
// Add hover handler
|
||||||
|
@ -496,11 +638,8 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
localVideoSrc = RTC.getVideoSrc(localVideo);
|
localVideoSrc = RTC.getVideoSrc(localVideo);
|
||||||
|
|
||||||
var myResourceJid = null;
|
var myResourceJid = xmpp.myResource();
|
||||||
if(connection.emuc.myroomjid)
|
|
||||||
{
|
|
||||||
myResourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
|
||||||
}
|
|
||||||
VideoLayout.updateLargeVideo(localVideoSrc, 0,
|
VideoLayout.updateLargeVideo(localVideoSrc, 0,
|
||||||
myResourceJid);
|
myResourceJid);
|
||||||
|
|
||||||
|
@ -539,7 +678,7 @@ var VideoLayout = (function (my) {
|
||||||
{
|
{
|
||||||
if(container.id == "localVideoWrapper")
|
if(container.id == "localVideoWrapper")
|
||||||
{
|
{
|
||||||
jid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
jid = xmpp.myResource();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -617,9 +756,9 @@ var VideoLayout = (function (my) {
|
||||||
largeVideoState.isVisible = $('#largeVideo').is(':visible');
|
largeVideoState.isVisible = $('#largeVideo').is(':visible');
|
||||||
largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid);
|
largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid);
|
||||||
if(jid2Ssrc[largeVideoState.userResourceJid] ||
|
if(jid2Ssrc[largeVideoState.userResourceJid] ||
|
||||||
(connection && connection.emuc.myroomjid &&
|
(xmpp.myResource() &&
|
||||||
largeVideoState.userResourceJid ===
|
largeVideoState.userResourceJid ===
|
||||||
Strophe.getResourceFromJid(connection.emuc.myroomjid))) {
|
xmpp.myResource())) {
|
||||||
largeVideoState.oldResourceJid = largeVideoState.userResourceJid;
|
largeVideoState.oldResourceJid = largeVideoState.userResourceJid;
|
||||||
} else {
|
} else {
|
||||||
largeVideoState.oldResourceJid = null;
|
largeVideoState.oldResourceJid = null;
|
||||||
|
@ -643,7 +782,7 @@ var VideoLayout = (function (my) {
|
||||||
var doUpdate = function () {
|
var doUpdate = function () {
|
||||||
|
|
||||||
Avatar.updateActiveSpeakerAvatarSrc(
|
Avatar.updateActiveSpeakerAvatarSrc(
|
||||||
connection.emuc.findJidFromResource(
|
xmpp.findJidFromResource(
|
||||||
largeVideoState.userResourceJid));
|
largeVideoState.userResourceJid));
|
||||||
|
|
||||||
if (!userChanged && largeVideoState.preload &&
|
if (!userChanged && largeVideoState.preload &&
|
||||||
|
@ -723,7 +862,7 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
if(userChanged) {
|
if(userChanged) {
|
||||||
Avatar.showUserAvatar(
|
Avatar.showUserAvatar(
|
||||||
connection.emuc.findJidFromResource(
|
xmpp.findJidFromResource(
|
||||||
largeVideoState.oldResourceJid));
|
largeVideoState.oldResourceJid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -738,7 +877,7 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Avatar.showUserAvatar(
|
Avatar.showUserAvatar(
|
||||||
connection.emuc.findJidFromResource(
|
xmpp.findJidFromResource(
|
||||||
largeVideoState.userResourceJid));
|
largeVideoState.userResourceJid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -877,7 +1016,7 @@ var VideoLayout = (function (my) {
|
||||||
focusedVideoInfo = null;
|
focusedVideoInfo = null;
|
||||||
if(focusResourceJid) {
|
if(focusResourceJid) {
|
||||||
Avatar.showUserAvatar(
|
Avatar.showUserAvatar(
|
||||||
connection.emuc.findJidFromResource(focusResourceJid));
|
xmpp.findJidFromResource(focusResourceJid));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -949,7 +1088,7 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
// If the peerJid is null then this video span couldn't be directly
|
// If the peerJid is null then this video span couldn't be directly
|
||||||
// associated with a participant (this could happen in the case of prezi).
|
// associated with a participant (this could happen in the case of prezi).
|
||||||
if (Moderator.isModerator() && peerJid !== null)
|
if (xmpp.isModerator() && peerJid !== null)
|
||||||
addRemoteVideoMenu(peerJid, container);
|
addRemoteVideoMenu(peerJid, container);
|
||||||
|
|
||||||
remotes.appendChild(container);
|
remotes.appendChild(container);
|
||||||
|
@ -1134,13 +1273,13 @@ var VideoLayout = (function (my) {
|
||||||
if (state == 'show')
|
if (state == 'show')
|
||||||
{
|
{
|
||||||
// peerContainer.css('-webkit-filter', '');
|
// peerContainer.css('-webkit-filter', '');
|
||||||
var jid = connection.emuc.findJidFromResource(resourceJid);
|
var jid = xmpp.findJidFromResource(resourceJid);
|
||||||
Avatar.showUserAvatar(jid, false);
|
Avatar.showUserAvatar(jid, false);
|
||||||
}
|
}
|
||||||
else // if (state == 'avatar')
|
else // if (state == 'avatar')
|
||||||
{
|
{
|
||||||
// peerContainer.css('-webkit-filter', 'grayscale(100%)');
|
// peerContainer.css('-webkit-filter', 'grayscale(100%)');
|
||||||
var jid = connection.emuc.findJidFromResource(resourceJid);
|
var jid = xmpp.findJidFromResource(resourceJid);
|
||||||
Avatar.showUserAvatar(jid, true);
|
Avatar.showUserAvatar(jid, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1166,8 +1305,7 @@ var VideoLayout = (function (my) {
|
||||||
if (name && nickname !== name) {
|
if (name && nickname !== name) {
|
||||||
nickname = name;
|
nickname = name;
|
||||||
window.localStorage.displayname = nickname;
|
window.localStorage.displayname = nickname;
|
||||||
connection.emuc.addDisplayNameToPresence(nickname);
|
xmpp.addToPresence("displayName", nickname);
|
||||||
connection.emuc.sendPresence();
|
|
||||||
|
|
||||||
Chat.setChatConversationMode(true);
|
Chat.setChatConversationMode(true);
|
||||||
}
|
}
|
||||||
|
@ -1238,7 +1376,7 @@ var VideoLayout = (function (my) {
|
||||||
*/
|
*/
|
||||||
my.showModeratorIndicator = function () {
|
my.showModeratorIndicator = function () {
|
||||||
|
|
||||||
var isModerator = Moderator.isModerator();
|
var isModerator = xmpp.isModerator();
|
||||||
if (isModerator) {
|
if (isModerator) {
|
||||||
var indicatorSpan = $('#localVideoContainer .focusindicator');
|
var indicatorSpan = $('#localVideoContainer .focusindicator');
|
||||||
|
|
||||||
|
@ -1247,7 +1385,10 @@ var VideoLayout = (function (my) {
|
||||||
createModeratorIndicatorElement(indicatorSpan[0]);
|
createModeratorIndicatorElement(indicatorSpan[0]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Object.keys(connection.emuc.members).forEach(function (jid) {
|
|
||||||
|
var members = xmpp.getMembers();
|
||||||
|
|
||||||
|
Object.keys(members).forEach(function (jid) {
|
||||||
|
|
||||||
if (Strophe.getResourceFromJid(jid) === 'focus') {
|
if (Strophe.getResourceFromJid(jid) === 'focus') {
|
||||||
// Skip server side focus
|
// Skip server side focus
|
||||||
|
@ -1263,7 +1404,7 @@ var VideoLayout = (function (my) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var member = connection.emuc.members[jid];
|
var member = members[jid];
|
||||||
|
|
||||||
if (member.role === 'moderator') {
|
if (member.role === 'moderator') {
|
||||||
// Remove menu if peer is moderator
|
// Remove menu if peer is moderator
|
||||||
|
@ -1435,7 +1576,7 @@ var VideoLayout = (function (my) {
|
||||||
var videoSpanId = null;
|
var videoSpanId = null;
|
||||||
var videoContainerId = null;
|
var videoContainerId = null;
|
||||||
if (resourceJid
|
if (resourceJid
|
||||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
|
=== xmpp.myResource()) {
|
||||||
videoSpanId = 'localVideoWrapper';
|
videoSpanId = 'localVideoWrapper';
|
||||||
videoContainerId = 'localVideoContainer';
|
videoContainerId = 'localVideoContainer';
|
||||||
}
|
}
|
||||||
|
@ -1478,7 +1619,7 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Avatar.showUserAvatar(
|
Avatar.showUserAvatar(
|
||||||
connection.emuc.findJidFromResource(resourceJid));
|
xmpp.findJidFromResource(resourceJid));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1603,7 +1744,7 @@ var VideoLayout = (function (my) {
|
||||||
lastNPickupJid = jid;
|
lastNPickupJid = jid;
|
||||||
$(document).trigger("pinnedendpointchanged", [jid]);
|
$(document).trigger("pinnedendpointchanged", [jid]);
|
||||||
}
|
}
|
||||||
} else if (jid == connection.emuc.myroomjid) {
|
} else if (jid == xmpp.myJid()) {
|
||||||
$("#localVideoContainer").click();
|
$("#localVideoContainer").click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1615,13 +1756,13 @@ var VideoLayout = (function (my) {
|
||||||
$(document).bind('audiomuted.muc', function (event, jid, isMuted) {
|
$(document).bind('audiomuted.muc', function (event, jid, isMuted) {
|
||||||
/*
|
/*
|
||||||
// FIXME: but focus can not mute in this case ? - check
|
// FIXME: but focus can not mute in this case ? - check
|
||||||
if (jid === connection.emuc.myroomjid) {
|
if (jid === xmpp.myJid()) {
|
||||||
|
|
||||||
// The local mute indicator is controlled locally
|
// The local mute indicator is controlled locally
|
||||||
return;
|
return;
|
||||||
}*/
|
}*/
|
||||||
var videoSpanId = null;
|
var videoSpanId = null;
|
||||||
if (jid === connection.emuc.myroomjid) {
|
if (jid === xmpp.myJid()) {
|
||||||
videoSpanId = 'localVideoContainer';
|
videoSpanId = 'localVideoContainer';
|
||||||
} else {
|
} else {
|
||||||
VideoLayout.ensurePeerContainerExists(jid);
|
VideoLayout.ensurePeerContainerExists(jid);
|
||||||
|
@ -1630,7 +1771,7 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
mutedAudios[jid] = isMuted;
|
mutedAudios[jid] = isMuted;
|
||||||
|
|
||||||
if (Moderator.isModerator()) {
|
if (xmpp.isModerator()) {
|
||||||
VideoLayout.updateRemoteVideoMenu(jid, isMuted);
|
VideoLayout.updateRemoteVideoMenu(jid, isMuted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1648,7 +1789,7 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
Avatar.showUserAvatar(jid, isMuted);
|
Avatar.showUserAvatar(jid, isMuted);
|
||||||
var videoSpanId = null;
|
var videoSpanId = null;
|
||||||
if (jid === connection.emuc.myroomjid) {
|
if (jid === xmpp.myJid()) {
|
||||||
videoSpanId = 'localVideoContainer';
|
videoSpanId = 'localVideoContainer';
|
||||||
} else {
|
} else {
|
||||||
VideoLayout.ensurePeerContainerExists(jid);
|
VideoLayout.ensurePeerContainerExists(jid);
|
||||||
|
@ -1662,11 +1803,11 @@ var VideoLayout = (function (my) {
|
||||||
/**
|
/**
|
||||||
* Display name changed.
|
* Display name changed.
|
||||||
*/
|
*/
|
||||||
$(document).bind('displaynamechanged',
|
my.onDisplayNameChanged =
|
||||||
function (event, jid, displayName, status) {
|
function (jid, displayName, status) {
|
||||||
var name = null;
|
var name = null;
|
||||||
if (jid === 'localVideoContainer'
|
if (jid === 'localVideoContainer'
|
||||||
|| jid === connection.emuc.myroomjid) {
|
|| jid === xmpp.myJid()) {
|
||||||
name = nickname;
|
name = nickname;
|
||||||
setDisplayName('localVideoContainer',
|
setDisplayName('localVideoContainer',
|
||||||
displayName);
|
displayName);
|
||||||
|
@ -1680,10 +1821,10 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(jid === 'localVideoContainer')
|
if(jid === 'localVideoContainer')
|
||||||
jid = connection.emuc.myroomjid;
|
jid = xmpp.myJid();
|
||||||
if(!name || name != displayName)
|
if(!name || name != displayName)
|
||||||
API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName});
|
API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName});
|
||||||
});
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On dominant speaker changed event.
|
* On dominant speaker changed event.
|
||||||
|
@ -1691,7 +1832,7 @@ var VideoLayout = (function (my) {
|
||||||
$(document).bind('dominantspeakerchanged', function (event, resourceJid) {
|
$(document).bind('dominantspeakerchanged', function (event, resourceJid) {
|
||||||
// We ignore local user events.
|
// We ignore local user events.
|
||||||
if (resourceJid
|
if (resourceJid
|
||||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
=== xmpp.myResource())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Update the current dominant speaker.
|
// Update the current dominant speaker.
|
||||||
|
@ -1822,7 +1963,7 @@ var VideoLayout = (function (my) {
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
console.log("Add to last N", resourceJid);
|
console.log("Add to last N", resourceJid);
|
||||||
|
|
||||||
var jid = connection.emuc.findJidFromResource(resourceJid);
|
var jid = xmpp.findJidFromResource(resourceJid);
|
||||||
var mediaStream = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
var mediaStream = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||||
var sel = $('#participant_' + resourceJid + '>video');
|
var sel = $('#participant_' + resourceJid + '>video');
|
||||||
|
|
||||||
|
@ -1855,7 +1996,7 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
var resource, container, src;
|
var resource, container, src;
|
||||||
var myResource
|
var myResource
|
||||||
= Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
= xmpp.myResource();
|
||||||
|
|
||||||
// Find out which endpoint to show in the large video.
|
// Find out which endpoint to show in the large video.
|
||||||
for (var i = 0; i < lastNEndpoints.length; i++) {
|
for (var i = 0; i < lastNEndpoints.length; i++) {
|
||||||
|
@ -1879,37 +2020,6 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).bind('videoactive.jingle', function (event, videoelem) {
|
|
||||||
if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
|
|
||||||
// ignore mixedmslabela0 and v0
|
|
||||||
|
|
||||||
videoelem.show();
|
|
||||||
VideoLayout.resizeThumbnails();
|
|
||||||
|
|
||||||
var videoParent = videoelem.parent();
|
|
||||||
var parentResourceJid = null;
|
|
||||||
if (videoParent)
|
|
||||||
parentResourceJid
|
|
||||||
= VideoLayout.getPeerContainerResourceJid(videoParent[0]);
|
|
||||||
|
|
||||||
// Update the large video to the last added video only if there's no
|
|
||||||
// current dominant, focused speaker or prezi playing or update it to
|
|
||||||
// the current dominant speaker.
|
|
||||||
if ((!focusedVideoInfo &&
|
|
||||||
!VideoLayout.getDominantSpeakerResourceJid() &&
|
|
||||||
!require("../prezi/Prezi").isPresentationVisible()) ||
|
|
||||||
(parentResourceJid &&
|
|
||||||
VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) {
|
|
||||||
VideoLayout.updateLargeVideo(
|
|
||||||
RTC.getVideoSrc(videoelem[0]),
|
|
||||||
1,
|
|
||||||
parentResourceJid);
|
|
||||||
}
|
|
||||||
|
|
||||||
VideoLayout.showModeratorIndicator();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) {
|
$(document).bind('simulcastlayerschanging', function (event, endpointSimulcastLayers) {
|
||||||
endpointSimulcastLayers.forEach(function (esl) {
|
endpointSimulcastLayers.forEach(function (esl) {
|
||||||
|
|
||||||
|
@ -1930,13 +2040,13 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
// Get session and stream from primary ssrc.
|
// Get session and stream from primary ssrc.
|
||||||
var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
|
var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
|
||||||
var session = res.session;
|
var sid = res.sid;
|
||||||
var electedStream = res.stream;
|
var electedStream = res.stream;
|
||||||
|
|
||||||
if (session && electedStream) {
|
if (sid && electedStream) {
|
||||||
var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
|
var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
|
||||||
|
|
||||||
console.info([esl, primarySSRC, msid, session, electedStream]);
|
console.info([esl, primarySSRC, msid, sid, electedStream]);
|
||||||
|
|
||||||
var msidParts = msid.split(' ');
|
var msidParts = msid.split(' ');
|
||||||
|
|
||||||
|
@ -1956,7 +2066,7 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Could not find a stream or a session.', session, electedStream);
|
console.error('Could not find a stream or a session.', sid, electedStream);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1988,17 +2098,17 @@ var VideoLayout = (function (my) {
|
||||||
|
|
||||||
// Get session and stream from primary ssrc.
|
// Get session and stream from primary ssrc.
|
||||||
var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
|
var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
|
||||||
var session = res.session;
|
var sid = res.sid;
|
||||||
var electedStream = res.stream;
|
var electedStream = res.stream;
|
||||||
|
|
||||||
if (session && electedStream) {
|
if (sid && electedStream) {
|
||||||
var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
|
var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
|
||||||
|
|
||||||
console.info('Switching simulcast substream.');
|
console.info('Switching simulcast substream.');
|
||||||
console.info([esl, primarySSRC, msid, session, electedStream]);
|
console.info([esl, primarySSRC, msid, sid, electedStream]);
|
||||||
|
|
||||||
var msidParts = msid.split(' ');
|
var msidParts = msid.split(' ');
|
||||||
var selRemoteVideo = $(['#', 'remoteVideo_', session.sid, '_', msidParts[0]].join(''));
|
var selRemoteVideo = $(['#', 'remoteVideo_', sid, '_', msidParts[0]].join(''));
|
||||||
|
|
||||||
var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC])
|
var updateLargeVideo = (Strophe.getResourceFromJid(ssrc2jid[primarySSRC])
|
||||||
== largeVideoState.userResourceJid);
|
== largeVideoState.userResourceJid);
|
||||||
|
@ -2035,7 +2145,7 @@ var VideoLayout = (function (my) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoId;
|
var videoId;
|
||||||
if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
if(resource == xmpp.myResource())
|
||||||
{
|
{
|
||||||
videoId = "localVideoContainer";
|
videoId = "localVideoContainer";
|
||||||
}
|
}
|
||||||
|
@ -2048,7 +2158,7 @@ var VideoLayout = (function (my) {
|
||||||
connectionIndicator.updatePopoverData();
|
connectionIndicator.updatePopoverData();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Could not find a stream or a session.', session, electedStream);
|
console.error('Could not find a stream or a sid.', sid, electedStream);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2063,8 +2173,8 @@ var VideoLayout = (function (my) {
|
||||||
if(object.resolution !== null)
|
if(object.resolution !== null)
|
||||||
{
|
{
|
||||||
resolution = object.resolution;
|
resolution = object.resolution;
|
||||||
object.resolution = resolution[connection.emuc.myroomjid];
|
object.resolution = resolution[xmpp.myJid()];
|
||||||
delete resolution[connection.emuc.myroomjid];
|
delete resolution[xmpp.myJid()];
|
||||||
}
|
}
|
||||||
updateStatsIndicator("localVideoContainer", percent, object);
|
updateStatsIndicator("localVideoContainer", percent, object);
|
||||||
for(var jid in resolution)
|
for(var jid in resolution)
|
||||||
|
|
|
@ -29,8 +29,7 @@ function startSendingStats() {
|
||||||
* Sends statistics to other participants
|
* Sends statistics to other participants
|
||||||
*/
|
*/
|
||||||
function sendStats() {
|
function sendStats() {
|
||||||
connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
|
xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
|
||||||
connection.emuc.sendPresence();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global $, alert, changeLocalVideo, chrome, config, connection, getConferenceHandler, getUserMediaWithConstraints */
|
/* global $, alert, changeLocalVideo, chrome, config, getConferenceHandler, getUserMediaWithConstraints */
|
||||||
/**
|
/**
|
||||||
* Indicates that desktop stream is currently in use(for toggle purpose).
|
* Indicates that desktop stream is currently in use(for toggle purpose).
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
|
|
@ -159,43 +159,19 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
|
||||||
|
|
||||||
// If we haven't receiving a "changed" event yet, then we must be receiving
|
// If we haven't receiving a "changed" event yet, then we must be receiving
|
||||||
// low quality (that the sender always streams).
|
// low quality (that the sender always streams).
|
||||||
if (!ssrc && connection.jingle) {
|
if(!ssrc)
|
||||||
var session;
|
{
|
||||||
var i, j, k;
|
var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||||
|
var remoteStream = remoteStreamObject.getOriginalStream();
|
||||||
var keys = Object.keys(connection.jingle.sessions);
|
|
||||||
for (i = 0; i < keys.length; i++) {
|
|
||||||
var sid = keys[i];
|
|
||||||
|
|
||||||
if (ssrc) {
|
|
||||||
// stream found, stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
session = connection.jingle.sessions[sid];
|
|
||||||
if (session.remoteStreams) {
|
|
||||||
for (j = 0; j < session.remoteStreams.length; j++) {
|
|
||||||
var remoteStream = session.remoteStreams[j];
|
|
||||||
|
|
||||||
if (ssrc) {
|
|
||||||
// stream found, stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var tracks = remoteStream.getVideoTracks();
|
var tracks = remoteStream.getVideoTracks();
|
||||||
if (tracks) {
|
if (tracks) {
|
||||||
for (k = 0; k < tracks.length; k++) {
|
for (var k = 0; k < tracks.length; k++) {
|
||||||
var track = tracks[k];
|
var track = tracks[k];
|
||||||
var msid = [remoteStream.id, track.id].join(' ');
|
var msid = [remoteStream.id, track.id].join(' ');
|
||||||
var _ssrc = this._remoteMaps.msid2ssrc[msid];
|
var _ssrc = this._remoteMaps.msid2ssrc[msid];
|
||||||
var _jid = ssrc2jid[_ssrc];
|
|
||||||
var quality = this._remoteMaps.msid2Quality[msid];
|
var quality = this._remoteMaps.msid2Quality[msid];
|
||||||
if (jid == _jid && quality == 0) {
|
if (quality == 0) {
|
||||||
ssrc = _ssrc;
|
ssrc = _ssrc;
|
||||||
// stream found, stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -206,27 +182,13 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
|
||||||
|
|
||||||
SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
|
SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
|
||||||
{
|
{
|
||||||
var session, electedStream;
|
var sid, electedStream;
|
||||||
var i, j, k;
|
var i, j, k;
|
||||||
if (connection.jingle) {
|
var jid = ssrc2jid[ssrc];
|
||||||
var keys = Object.keys(connection.jingle.sessions);
|
if(jid)
|
||||||
for (i = 0; i < keys.length; i++) {
|
{
|
||||||
var sid = keys[i];
|
var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||||
|
var remoteStream = remoteStreamObject.getOriginalStream();
|
||||||
if (electedStream) {
|
|
||||||
// stream found, stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
session = connection.jingle.sessions[sid];
|
|
||||||
if (session.remoteStreams) {
|
|
||||||
for (j = 0; j < session.remoteStreams.length; j++) {
|
|
||||||
var remoteStream = session.remoteStreams[j];
|
|
||||||
|
|
||||||
if (electedStream) {
|
|
||||||
// stream found, stop.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
var tracks = remoteStream.getVideoTracks();
|
var tracks = remoteStream.getVideoTracks();
|
||||||
if (tracks) {
|
if (tracks) {
|
||||||
for (k = 0; k < tracks.length; k++) {
|
for (k = 0; k < tracks.length; k++) {
|
||||||
|
@ -235,18 +197,17 @@ SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
|
||||||
var tmp = this._remoteMaps.msid2ssrc[msid];
|
var tmp = this._remoteMaps.msid2ssrc[msid];
|
||||||
if (tmp == ssrc) {
|
if (tmp == ssrc) {
|
||||||
electedStream = new webkitMediaStream([track]);
|
electedStream = new webkitMediaStream([track]);
|
||||||
|
sid = remoteStreamObject.sid;
|
||||||
// stream found, stop.
|
// stream found, stop.
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session: session,
|
sid: sid,
|
||||||
stream: electedStream
|
stream: electedStream
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -329,30 +329,9 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
|
||||||
};
|
};
|
||||||
|
|
||||||
StatsCollector.prototype.logStats = function () {
|
StatsCollector.prototype.logStats = function () {
|
||||||
if (!focusMucJid) {
|
|
||||||
|
if(!xmpp.sendLogs(this.statsToBeLogged))
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
var deflate = true;
|
|
||||||
|
|
||||||
var content = JSON.stringify(this.statsToBeLogged);
|
|
||||||
if (deflate) {
|
|
||||||
content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
|
|
||||||
}
|
|
||||||
content = Base64.encode(content);
|
|
||||||
|
|
||||||
// XEP-0337-ish
|
|
||||||
var message = $msg({to: focusMucJid, type: 'normal'});
|
|
||||||
message.c('log', { xmlns: 'urn:xmpp:eventlog',
|
|
||||||
id: 'PeerConnectionStats'});
|
|
||||||
message.c('message').t(content).up();
|
|
||||||
if (deflate) {
|
|
||||||
message.c('tag', {name: "deflated", value: "true"}).up();
|
|
||||||
}
|
|
||||||
message.up();
|
|
||||||
|
|
||||||
connection.send(message);
|
|
||||||
|
|
||||||
// Reset the stats
|
// Reset the stats
|
||||||
this.statsToBeLogged.stats = {};
|
this.statsToBeLogged.stats = {};
|
||||||
this.statsToBeLogged.timestamps = [];
|
this.statsToBeLogged.timestamps = [];
|
||||||
|
@ -700,7 +679,7 @@ StatsCollector.prototype.processAudioLevelReport = function ()
|
||||||
// but it seems to vary between 0 and around 32k.
|
// but it seems to vary between 0 and around 32k.
|
||||||
audioLevel = audioLevel / 32767;
|
audioLevel = audioLevel / 32767;
|
||||||
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
|
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
|
||||||
if(jid != connection.emuc.myroomjid)
|
if(jid != xmpp.myJid())
|
||||||
this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
|
this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,14 @@ function onStreamCreated(stream)
|
||||||
localStats.start();
|
localStats.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onDisposeConference(onUnload) {
|
||||||
|
stopRemote();
|
||||||
|
if(onUnload) {
|
||||||
|
stopLocal();
|
||||||
|
eventEmitter.removeAllListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
var statistics =
|
var statistics =
|
||||||
{
|
{
|
||||||
|
@ -117,19 +125,12 @@ var statistics =
|
||||||
startRemoteStats(event.peerconnection);
|
startRemoteStats(event.peerconnection);
|
||||||
},
|
},
|
||||||
|
|
||||||
onDisposeConference: function (onUnload) {
|
|
||||||
stopRemote();
|
|
||||||
if(onUnload) {
|
|
||||||
stopLocal();
|
|
||||||
eventEmitter.removeAllListeners();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
start: function () {
|
start: function () {
|
||||||
this.addConnectionStatsListener(connectionquality.updateLocalStats);
|
this.addConnectionStatsListener(connectionquality.updateLocalStats);
|
||||||
this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
|
this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
|
||||||
RTC.addStreamListener(onStreamCreated,
|
RTC.addStreamListener(onStreamCreated,
|
||||||
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||||
|
xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
/* jshint -W117 */
|
/* jshint -W117 */
|
||||||
|
var TraceablePeerConnection = require("./TraceablePeerConnection");
|
||||||
|
var SDPDiffer = require("./SDPDiffer");
|
||||||
|
var SDPUtil = require("./SDPUtil");
|
||||||
|
var SDP = require("./SDP");
|
||||||
|
|
||||||
// Jingle stuff
|
// Jingle stuff
|
||||||
function JingleSession(me, sid, connection) {
|
function JingleSession(me, sid, connection, service) {
|
||||||
this.me = me;
|
this.me = me;
|
||||||
this.sid = sid;
|
this.sid = sid;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
|
@ -12,13 +17,13 @@ function JingleSession(me, sid, connection) {
|
||||||
this.localSDP = null;
|
this.localSDP = null;
|
||||||
this.remoteSDP = null;
|
this.remoteSDP = null;
|
||||||
this.relayedStreams = [];
|
this.relayedStreams = [];
|
||||||
this.remoteStreams = [];
|
|
||||||
this.startTime = null;
|
this.startTime = null;
|
||||||
this.stopTime = null;
|
this.stopTime = null;
|
||||||
this.media_constraints = null;
|
this.media_constraints = null;
|
||||||
this.pc_constraints = null;
|
this.pc_constraints = null;
|
||||||
this.ice_config = {};
|
this.ice_config = {};
|
||||||
this.drip_container = [];
|
this.drip_container = [];
|
||||||
|
this.service = service;
|
||||||
|
|
||||||
this.usetrickle = true;
|
this.usetrickle = true;
|
||||||
this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
|
this.usepranswer = false; // early transport warmup -- mind you, this might fail. depends on webrtc issue 1718
|
||||||
|
@ -73,16 +78,11 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
|
||||||
self.sendIceCandidate(event.candidate);
|
self.sendIceCandidate(event.candidate);
|
||||||
};
|
};
|
||||||
this.peerconnection.onaddstream = function (event) {
|
this.peerconnection.onaddstream = function (event) {
|
||||||
self.remoteStreams.push(event.stream);
|
|
||||||
console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
|
console.log("REMOTE STREAM ADDED: " + event.stream + " - " + event.stream.id);
|
||||||
$(document).trigger('remotestreamadded.jingle', [event, self.sid]);
|
self.remoteStreamAdded(event);
|
||||||
};
|
};
|
||||||
this.peerconnection.onremovestream = function (event) {
|
this.peerconnection.onremovestream = function (event) {
|
||||||
// Remove the stream from remoteStreams
|
// Remove the stream from remoteStreams
|
||||||
var streamIdx = self.remoteStreams.indexOf(event.stream);
|
|
||||||
if(streamIdx !== -1){
|
|
||||||
self.remoteStreams.splice(streamIdx, 1);
|
|
||||||
}
|
|
||||||
// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
|
// FIXME: remotestreamremoved.jingle not defined anywhere(unused)
|
||||||
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
|
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
|
||||||
};
|
};
|
||||||
|
@ -99,7 +99,7 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
|
||||||
this.stopTime = new Date();
|
this.stopTime = new Date();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
|
onIceConnectionStateChange(self.sid, self);
|
||||||
};
|
};
|
||||||
// add any local and relayed stream
|
// add any local and relayed stream
|
||||||
RTC.localStreams.forEach(function(stream) {
|
RTC.localStreams.forEach(function(stream) {
|
||||||
|
@ -110,6 +110,49 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function onIceConnectionStateChange(sid, session) {
|
||||||
|
switch (session.peerconnection.iceConnectionState) {
|
||||||
|
case 'checking':
|
||||||
|
session.timeChecking = (new Date()).getTime();
|
||||||
|
session.firstconnect = true;
|
||||||
|
break;
|
||||||
|
case 'completed': // on caller side
|
||||||
|
case 'connected':
|
||||||
|
if (session.firstconnect) {
|
||||||
|
session.firstconnect = false;
|
||||||
|
var metadata = {};
|
||||||
|
metadata.setupTime
|
||||||
|
= (new Date()).getTime() - session.timeChecking;
|
||||||
|
session.peerconnection.getStats(function (res) {
|
||||||
|
if(res && res.result) {
|
||||||
|
res.result().forEach(function (report) {
|
||||||
|
if (report.type == 'googCandidatePair' &&
|
||||||
|
report.stat('googActiveConnection') == 'true') {
|
||||||
|
metadata.localCandidateType
|
||||||
|
= report.stat('googLocalCandidateType');
|
||||||
|
metadata.remoteCandidateType
|
||||||
|
= report.stat('googRemoteCandidateType');
|
||||||
|
|
||||||
|
// log pair as well so we can get nice pie
|
||||||
|
// charts
|
||||||
|
metadata.candidatePair
|
||||||
|
= report.stat('googLocalCandidateType') +
|
||||||
|
';' +
|
||||||
|
report.stat('googRemoteCandidateType');
|
||||||
|
|
||||||
|
if (report.stat('googRemoteAddress').indexOf('[') === 0)
|
||||||
|
{
|
||||||
|
metadata.ipv6 = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
JingleSession.prototype.accept = function () {
|
JingleSession.prototype.accept = function () {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.state = 'active';
|
this.state = 'active';
|
||||||
|
@ -145,12 +188,13 @@ JingleSession.prototype.accept = function () {
|
||||||
// FIXME: change any inactive to sendrecv or whatever they were originally
|
// FIXME: change any inactive to sendrecv or whatever they were originally
|
||||||
sdp = sdp.replace('a=inactive', 'a=sendrecv');
|
sdp = sdp.replace('a=inactive', 'a=sendrecv');
|
||||||
}
|
}
|
||||||
|
var self = this;
|
||||||
this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
|
this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
|
||||||
function () {
|
function () {
|
||||||
//console.log('setLocalDescription success');
|
//console.log('setLocalDescription success');
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
self.setLocalDescription();
|
||||||
|
|
||||||
this.connection.sendIQ(accept,
|
self.connection.sendIQ(accept,
|
||||||
function () {
|
function () {
|
||||||
var ack = {};
|
var ack = {};
|
||||||
ack.source = 'answer';
|
ack.source = 'answer';
|
||||||
|
@ -347,8 +391,8 @@ JingleSession.prototype.createdOffer = function (sdp) {
|
||||||
action: 'session-initiate',
|
action: 'session-initiate',
|
||||||
initiator: this.initiator,
|
initiator: this.initiator,
|
||||||
sid: this.sid});
|
sid: this.sid});
|
||||||
this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
|
self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
|
||||||
this.connection.sendIQ(init,
|
self.connection.sendIQ(init,
|
||||||
function () {
|
function () {
|
||||||
var ack = {};
|
var ack = {};
|
||||||
ack.source = 'offer';
|
ack.source = 'offer';
|
||||||
|
@ -369,13 +413,11 @@ JingleSession.prototype.createdOffer = function (sdp) {
|
||||||
sdp.sdp = this.localSDP.raw;
|
sdp.sdp = this.localSDP.raw;
|
||||||
this.peerconnection.setLocalDescription(sdp,
|
this.peerconnection.setLocalDescription(sdp,
|
||||||
function () {
|
function () {
|
||||||
if(this.usetrickle)
|
if(self.usetrickle)
|
||||||
{
|
{
|
||||||
sendJingle();
|
sendJingle();
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
||||||
}
|
}
|
||||||
else
|
self.setLocalDescription();
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
||||||
//console.log('setLocalDescription success');
|
//console.log('setLocalDescription success');
|
||||||
},
|
},
|
||||||
function (e) {
|
function (e) {
|
||||||
|
@ -587,7 +629,7 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
|
||||||
var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
|
var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
|
||||||
var publicLocalSDP = new SDP(publicLocalDesc.sdp);
|
var publicLocalSDP = new SDP(publicLocalDesc.sdp);
|
||||||
publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
|
publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
|
||||||
this.connection.sendIQ(accept,
|
self.connection.sendIQ(accept,
|
||||||
function () {
|
function () {
|
||||||
var ack = {};
|
var ack = {};
|
||||||
ack.source = 'answer';
|
ack.source = 'answer';
|
||||||
|
@ -610,10 +652,8 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
|
||||||
//console.log('setLocalDescription success');
|
//console.log('setLocalDescription success');
|
||||||
if (self.usetrickle && !self.usepranswer) {
|
if (self.usetrickle && !self.usepranswer) {
|
||||||
sendJingle();
|
sendJingle();
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
||||||
}
|
}
|
||||||
else
|
self.setLocalDescription();
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
|
||||||
},
|
},
|
||||||
function (e) {
|
function (e) {
|
||||||
console.error('setLocalDescription failed', e);
|
console.error('setLocalDescription failed', e);
|
||||||
|
@ -799,7 +839,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
|
||||||
if (this.peerconnection.signalingState == 'closed') return;
|
if (this.peerconnection.signalingState == 'closed') return;
|
||||||
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
|
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
|
// There is nothing to do since scheduled job might have been executed by another succeeding call
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
this.setLocalDescription();
|
||||||
if(successCallback){
|
if(successCallback){
|
||||||
successCallback();
|
successCallback();
|
||||||
}
|
}
|
||||||
|
@ -889,7 +929,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
|
||||||
self.peerconnection.setLocalDescription(modifiedAnswer,
|
self.peerconnection.setLocalDescription(modifiedAnswer,
|
||||||
function() {
|
function() {
|
||||||
//console.log('modified setLocalDescription ok');
|
//console.log('modified setLocalDescription ok');
|
||||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
self.setLocalDescription();
|
||||||
if(successCallback){
|
if(successCallback){
|
||||||
successCallback();
|
successCallback();
|
||||||
}
|
}
|
||||||
|
@ -1064,12 +1104,20 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
|
||||||
} else if (this.videoMuteByUser) {
|
} else if (this.videoMuteByUser) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var localCallback = function (mute) {
|
||||||
|
self.connection.emuc.addVideoInfoToPresence(mute);
|
||||||
|
self.connection.emuc.sendPresence();
|
||||||
|
return callback(mute)
|
||||||
|
};
|
||||||
|
|
||||||
if (mute == RTC.localVideo.isMuted())
|
if (mute == RTC.localVideo.isMuted())
|
||||||
{
|
{
|
||||||
// Even if no change occurs, the specified callback is to be executed.
|
// Even if no change occurs, the specified callback is to be executed.
|
||||||
// The specified callback may, optionally, return a successCallback
|
// The specified callback may, optionally, return a successCallback
|
||||||
// which is to be executed as well.
|
// which is to be executed as well.
|
||||||
var successCallback = callback(mute);
|
var successCallback = localCallback(mute);
|
||||||
|
|
||||||
if (successCallback) {
|
if (successCallback) {
|
||||||
successCallback();
|
successCallback();
|
||||||
|
@ -1079,14 +1127,14 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
|
||||||
|
|
||||||
this.hardMuteVideo(mute);
|
this.hardMuteVideo(mute);
|
||||||
|
|
||||||
this.modifySources(callback(mute));
|
this.modifySources(localCallback(mute));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// SDP-based mute by going recvonly/sendrecv
|
// SDP-based mute by going recvonly/sendrecv
|
||||||
// FIXME: should probably black out the screen as well
|
// FIXME: should probably black out the screen as well
|
||||||
JingleSession.prototype.toggleVideoMute = function (callback) {
|
JingleSession.prototype.toggleVideoMute = function (callback) {
|
||||||
setVideoMute(RTC.localVideo.isMuted(), callback);
|
this.service.setVideoMute(RTC.localVideo.isMuted(), callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
JingleSession.prototype.hardMuteVideo = function (muted) {
|
JingleSession.prototype.hardMuteVideo = function (muted) {
|
||||||
|
@ -1172,8 +1220,170 @@ JingleSession.onJingleError = function (session, error)
|
||||||
|
|
||||||
JingleSession.onJingleFatalError = function (session, error)
|
JingleSession.onJingleFatalError = function (session, error)
|
||||||
{
|
{
|
||||||
sessionTerminated = true;
|
this.service.sessionTerminated = true;
|
||||||
connection.emuc.doLeave();
|
connection.emuc.doLeave();
|
||||||
UI.messageHandler.showError( "Sorry",
|
UI.messageHandler.showError( "Sorry",
|
||||||
"Internal application error[setRemoteDescription]");
|
"Internal application error[setRemoteDescription]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JingleSession.prototype.setLocalDescription = function () {
|
||||||
|
// put our ssrcs into presence so other clients can identify our stream
|
||||||
|
var newssrcs = [];
|
||||||
|
var media = simulcast.parseMedia(this.peerconnection.localDescription);
|
||||||
|
media.forEach(function (media) {
|
||||||
|
|
||||||
|
if(Object.keys(media.sources).length > 0) {
|
||||||
|
// TODO(gp) maybe exclude FID streams?
|
||||||
|
Object.keys(media.sources).forEach(function (ssrc) {
|
||||||
|
newssrcs.push({
|
||||||
|
'ssrc': ssrc,
|
||||||
|
'type': media.type,
|
||||||
|
'direction': media.direction
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type])
|
||||||
|
{
|
||||||
|
newssrcs.push({
|
||||||
|
'ssrc': this.localStreamsSSRC[media.type],
|
||||||
|
'type': media.type,
|
||||||
|
'direction': media.direction
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('new ssrcs', newssrcs);
|
||||||
|
|
||||||
|
// Have to clear presence map to get rid of removed streams
|
||||||
|
this.connection.emuc.clearPresenceMedia();
|
||||||
|
|
||||||
|
if (newssrcs.length > 0) {
|
||||||
|
for (var i = 1; i <= newssrcs.length; i ++) {
|
||||||
|
// Change video type to screen
|
||||||
|
if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) {
|
||||||
|
newssrcs[i-1].type = 'screen';
|
||||||
|
}
|
||||||
|
this.connection.emuc.addMediaToPresence(i,
|
||||||
|
newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.emuc.sendPresence();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||||
|
function sendKeyframe(pc) {
|
||||||
|
console.log('sendkeyframe', pc.iceConnectionState);
|
||||||
|
if (pc.iceConnectionState !== 'connected') return; // safe...
|
||||||
|
pc.setRemoteDescription(
|
||||||
|
pc.remoteDescription,
|
||||||
|
function () {
|
||||||
|
pc.createAnswer(
|
||||||
|
function (modifiedAnswer) {
|
||||||
|
pc.setLocalDescription(
|
||||||
|
modifiedAnswer,
|
||||||
|
function () {
|
||||||
|
// noop
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('triggerKeyframe setLocalDescription failed', error);
|
||||||
|
UI.messageHandler.showError();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('triggerKeyframe createAnswer failed', error);
|
||||||
|
UI.messageHandler.showError();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('triggerKeyframe setRemoteDescription failed', error);
|
||||||
|
UI.messageHandler.showError();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JingleSession.prototype.remoteStreamAdded = function (data) {
|
||||||
|
var self = this;
|
||||||
|
var thessrc;
|
||||||
|
|
||||||
|
// look up an associated JID for a stream id
|
||||||
|
if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
|
||||||
|
// look only at a=ssrc: and _not_ at a=ssrc-group: lines
|
||||||
|
|
||||||
|
var ssrclines
|
||||||
|
= SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
|
||||||
|
ssrclines = ssrclines.filter(function (line) {
|
||||||
|
// NOTE(gp) previously we filtered on the mslabel, but that property
|
||||||
|
// is not always present.
|
||||||
|
// return line.indexOf('mslabel:' + data.stream.label) !== -1;
|
||||||
|
|
||||||
|
return ((line.indexOf('msid:' + data.stream.id) !== -1));
|
||||||
|
});
|
||||||
|
if (ssrclines.length) {
|
||||||
|
thessrc = ssrclines[0].substring(7).split(' ')[0];
|
||||||
|
|
||||||
|
// We signal our streams (through Jingle to the focus) before we set
|
||||||
|
// our presence (through which peers associate remote streams to
|
||||||
|
// jids). So, it might arrive that a remote stream is added but
|
||||||
|
// ssrc2jid is not yet updated and thus data.peerjid cannot be
|
||||||
|
// successfully set. Here we wait for up to a second for the
|
||||||
|
// presence to arrive.
|
||||||
|
|
||||||
|
if (!ssrc2jid[thessrc]) {
|
||||||
|
// TODO(gp) limit wait duration to 1 sec.
|
||||||
|
setTimeout(function(d) {
|
||||||
|
return function() {
|
||||||
|
self.remoteStreamAdded(d);
|
||||||
|
}
|
||||||
|
}(data), 250);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ok to overwrite the one from focus? might save work in colibri.js
|
||||||
|
console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
|
||||||
|
if (ssrc2jid[thessrc]) {
|
||||||
|
data.peerjid = ssrc2jid[thessrc];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO: this code should be removed when firefox implement multistream support
|
||||||
|
if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
|
||||||
|
{
|
||||||
|
if((notReceivedSSRCs.length == 0) ||
|
||||||
|
!ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
|
||||||
|
{
|
||||||
|
// TODO(gp) limit wait duration to 1 sec.
|
||||||
|
setTimeout(function(d) {
|
||||||
|
return function() {
|
||||||
|
self.remoteStreamAdded(d);
|
||||||
|
}
|
||||||
|
}(data), 250);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
thessrc = notReceivedSSRCs.pop();
|
||||||
|
if (ssrc2jid[thessrc]) {
|
||||||
|
data.peerjid = ssrc2jid[thessrc];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RTC.createRemoteStream(data, this.sid, thessrc);
|
||||||
|
|
||||||
|
var isVideo = data.stream.getVideoTracks().length > 0;
|
||||||
|
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||||
|
if (isVideo &&
|
||||||
|
data.peerjid && this.peerjid === data.peerjid &&
|
||||||
|
data.stream.getVideoTracks().length === 0 &&
|
||||||
|
RTC.localVideo.getTracks().length > 0) {
|
||||||
|
window.setTimeout(function () {
|
||||||
|
sendKeyframe(self.peerconnection);
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = JingleSession;
|
|
@ -1,4 +1,6 @@
|
||||||
/* jshint -W117 */
|
/* jshint -W117 */
|
||||||
|
var SDPUtil = require("./SDPUtil");
|
||||||
|
|
||||||
// SDP STUFF
|
// SDP STUFF
|
||||||
function SDP(sdp) {
|
function SDP(sdp) {
|
||||||
this.media = sdp.split('\r\nm=');
|
this.media = sdp.split('\r\nm=');
|
||||||
|
@ -71,169 +73,6 @@ SDP.prototype.containsSSRC = function(ssrc) {
|
||||||
return contains;
|
return contains;
|
||||||
};
|
};
|
||||||
|
|
||||||
function SDPDiffer(mySDP, otherSDP) {
|
|
||||||
this.mySDP = mySDP;
|
|
||||||
this.otherSDP = otherSDP;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
|
|
||||||
* @param otherSdp the other SDP to check ssrc with.
|
|
||||||
*/
|
|
||||||
SDPDiffer.prototype.getNewMedia = function() {
|
|
||||||
|
|
||||||
// this could be useful in Array.prototype.
|
|
||||||
function arrayEquals(array) {
|
|
||||||
// if the other array is a falsy value, return
|
|
||||||
if (!array)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// compare lengths - can save a lot of time
|
|
||||||
if (this.length != array.length)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
for (var i = 0, l=this.length; i < l; i++) {
|
|
||||||
// Check if we have nested arrays
|
|
||||||
if (this[i] instanceof Array && array[i] instanceof Array) {
|
|
||||||
// recurse into the nested arrays
|
|
||||||
if (!this[i].equals(array[i]))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (this[i] != array[i]) {
|
|
||||||
// Warning - two different object instances will never be equal: {x:20} != {x:20}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var myMedias = this.mySDP.getMediaSsrcMap();
|
|
||||||
var othersMedias = this.otherSDP.getMediaSsrcMap();
|
|
||||||
var newMedia = {};
|
|
||||||
Object.keys(othersMedias).forEach(function(othersMediaIdx) {
|
|
||||||
var myMedia = myMedias[othersMediaIdx];
|
|
||||||
var othersMedia = othersMedias[othersMediaIdx];
|
|
||||||
if(!myMedia && othersMedia) {
|
|
||||||
// Add whole channel
|
|
||||||
newMedia[othersMediaIdx] = othersMedia;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Look for new ssrcs accross the channel
|
|
||||||
Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
|
|
||||||
if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
|
|
||||||
// Allocate channel if we've found ssrc that doesn't exist in our channel
|
|
||||||
if(!newMedia[othersMediaIdx]){
|
|
||||||
newMedia[othersMediaIdx] = {
|
|
||||||
mediaindex: othersMedia.mediaindex,
|
|
||||||
mid: othersMedia.mid,
|
|
||||||
ssrcs: {},
|
|
||||||
ssrcGroups: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Look for new ssrc groups across the channels
|
|
||||||
othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
|
|
||||||
|
|
||||||
// try to match the other ssrc-group with an ssrc-group of ours
|
|
||||||
var matched = false;
|
|
||||||
for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
|
|
||||||
var mySsrcGroup = myMedia.ssrcGroups[i];
|
|
||||||
if (otherSsrcGroup.semantics == mySsrcGroup.semantics
|
|
||||||
&& arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
|
|
||||||
|
|
||||||
matched = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!matched) {
|
|
||||||
// Allocate channel if we've found an ssrc-group that doesn't
|
|
||||||
// exist in our channel
|
|
||||||
|
|
||||||
if(!newMedia[othersMediaIdx]){
|
|
||||||
newMedia[othersMediaIdx] = {
|
|
||||||
mediaindex: othersMedia.mediaindex,
|
|
||||||
mid: othersMedia.mid,
|
|
||||||
ssrcs: {},
|
|
||||||
ssrcGroups: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return newMedia;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends SSRC update IQ.
|
|
||||||
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
|
|
||||||
* @param sid session identifier that will be put into the IQ.
|
|
||||||
* @param initiator initiator identifier.
|
|
||||||
* @param toJid destination Jid
|
|
||||||
* @param isAdd indicates if this is remove or add operation.
|
|
||||||
*/
|
|
||||||
SDPDiffer.prototype.toJingle = function(modify) {
|
|
||||||
var sdpMediaSsrcs = this.getNewMedia();
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// FIXME: only announce video ssrcs since we mix audio and dont need
|
|
||||||
// the audio ssrcs therefore
|
|
||||||
var modified = false;
|
|
||||||
Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
|
|
||||||
modified = true;
|
|
||||||
var media = sdpMediaSsrcs[mediaindex];
|
|
||||||
modify.c('content', {name: media.mid});
|
|
||||||
|
|
||||||
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
|
|
||||||
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
|
|
||||||
// generate sources from lines
|
|
||||||
Object.keys(media.ssrcs).forEach(function(ssrcNum) {
|
|
||||||
var mediaSsrc = media.ssrcs[ssrcNum];
|
|
||||||
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
|
|
||||||
modify.attrs({ssrc: mediaSsrc.ssrc});
|
|
||||||
// iterate over ssrc lines
|
|
||||||
mediaSsrc.lines.forEach(function (line) {
|
|
||||||
var idx = line.indexOf(' ');
|
|
||||||
var kv = line.substr(idx + 1);
|
|
||||||
modify.c('parameter');
|
|
||||||
if (kv.indexOf(':') == -1) {
|
|
||||||
modify.attrs({ name: kv });
|
|
||||||
} else {
|
|
||||||
modify.attrs({ name: kv.split(':', 2)[0] });
|
|
||||||
modify.attrs({ value: kv.split(':', 2)[1] });
|
|
||||||
}
|
|
||||||
modify.up(); // end of parameter
|
|
||||||
});
|
|
||||||
modify.up(); // end of source
|
|
||||||
});
|
|
||||||
|
|
||||||
// generate source groups from lines
|
|
||||||
media.ssrcGroups.forEach(function(ssrcGroup) {
|
|
||||||
if (ssrcGroup.ssrcs.length != 0) {
|
|
||||||
|
|
||||||
modify.c('ssrc-group', {
|
|
||||||
semantics: ssrcGroup.semantics,
|
|
||||||
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
|
|
||||||
});
|
|
||||||
|
|
||||||
ssrcGroup.ssrcs.forEach(function (ssrc) {
|
|
||||||
modify.c('source', { ssrc: ssrc })
|
|
||||||
.up(); // end of source
|
|
||||||
});
|
|
||||||
modify.up(); // end of ssrc-group
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
modify.up(); // end of description
|
|
||||||
modify.up(); // end of content
|
|
||||||
});
|
|
||||||
|
|
||||||
return modified;
|
|
||||||
};
|
|
||||||
|
|
||||||
// remove iSAC and CN from SDP
|
// remove iSAC and CN from SDP
|
||||||
SDP.prototype.mangle = function () {
|
SDP.prototype.mangle = function () {
|
||||||
|
@ -776,352 +615,6 @@ SDP.prototype.jingle2media = function (content) {
|
||||||
return media;
|
return media;
|
||||||
};
|
};
|
||||||
|
|
||||||
SDPUtil = {
|
|
||||||
iceparams: function (mediadesc, sessiondesc) {
|
|
||||||
var data = null;
|
|
||||||
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
|
|
||||||
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
|
|
||||||
data = {
|
|
||||||
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
|
|
||||||
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_iceufrag: function (line) {
|
|
||||||
return line.substring(12);
|
|
||||||
},
|
|
||||||
build_iceufrag: function (frag) {
|
|
||||||
return 'a=ice-ufrag:' + frag;
|
|
||||||
},
|
|
||||||
parse_icepwd: function (line) {
|
|
||||||
return line.substring(10);
|
|
||||||
},
|
|
||||||
build_icepwd: function (pwd) {
|
|
||||||
return 'a=ice-pwd:' + pwd;
|
|
||||||
},
|
|
||||||
parse_mid: function (line) {
|
|
||||||
return line.substring(6);
|
|
||||||
},
|
|
||||||
parse_mline: function (line) {
|
|
||||||
var parts = line.substring(2).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.media = parts.shift();
|
|
||||||
data.port = parts.shift();
|
|
||||||
data.proto = parts.shift();
|
|
||||||
if (parts[parts.length - 1] === '') { // trailing whitespace
|
|
||||||
parts.pop();
|
|
||||||
}
|
|
||||||
data.fmt = parts;
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
build_mline: function (mline) {
|
|
||||||
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
|
|
||||||
},
|
|
||||||
parse_rtpmap: function (line) {
|
|
||||||
var parts = line.substring(9).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.id = parts.shift();
|
|
||||||
parts = parts[0].split('/');
|
|
||||||
data.name = parts.shift();
|
|
||||||
data.clockrate = parts.shift();
|
|
||||||
data.channels = parts.length ? parts.shift() : '1';
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
|
|
||||||
* @param line eg. "a=sctpmap:5000 webrtc-datachannel"
|
|
||||||
* @returns [SCTP port number, protocol, streams]
|
|
||||||
*/
|
|
||||||
parse_sctpmap: function (line)
|
|
||||||
{
|
|
||||||
var parts = line.substring(10).split(' ');
|
|
||||||
var sctpPort = parts[0];
|
|
||||||
var protocol = parts[1];
|
|
||||||
// Stream count is optional
|
|
||||||
var streamCount = parts.length > 2 ? parts[2] : null;
|
|
||||||
return [sctpPort, protocol, streamCount];// SCTP port
|
|
||||||
},
|
|
||||||
build_rtpmap: function (el) {
|
|
||||||
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
|
|
||||||
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
|
|
||||||
line += '/' + el.getAttribute('channels');
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
},
|
|
||||||
parse_crypto: function (line) {
|
|
||||||
var parts = line.substring(9).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.tag = parts.shift();
|
|
||||||
data['crypto-suite'] = parts.shift();
|
|
||||||
data['key-params'] = parts.shift();
|
|
||||||
if (parts.length) {
|
|
||||||
data['session-params'] = parts.join(' ');
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_fingerprint: function (line) { // RFC 4572
|
|
||||||
var parts = line.substring(14).split(' '),
|
|
||||||
data = {};
|
|
||||||
data.hash = parts.shift();
|
|
||||||
data.fingerprint = parts.shift();
|
|
||||||
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_fmtp: function (line) {
|
|
||||||
var parts = line.split(' '),
|
|
||||||
i, key, value,
|
|
||||||
data = [];
|
|
||||||
parts.shift();
|
|
||||||
parts = parts.join(' ').split(';');
|
|
||||||
for (i = 0; i < parts.length; i++) {
|
|
||||||
key = parts[i].split('=')[0];
|
|
||||||
while (key.length && key[0] == ' ') {
|
|
||||||
key = key.substring(1);
|
|
||||||
}
|
|
||||||
value = parts[i].split('=')[1];
|
|
||||||
if (key && value) {
|
|
||||||
data.push({name: key, value: value});
|
|
||||||
} else if (key) {
|
|
||||||
// rfc 4733 (DTMF) style stuff
|
|
||||||
data.push({name: '', value: key});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_icecandidate: function (line) {
|
|
||||||
var candidate = {},
|
|
||||||
elems = line.split(' ');
|
|
||||||
candidate.foundation = elems[0].substring(12);
|
|
||||||
candidate.component = elems[1];
|
|
||||||
candidate.protocol = elems[2].toLowerCase();
|
|
||||||
candidate.priority = elems[3];
|
|
||||||
candidate.ip = elems[4];
|
|
||||||
candidate.port = elems[5];
|
|
||||||
// elems[6] => "typ"
|
|
||||||
candidate.type = elems[7];
|
|
||||||
candidate.generation = 0; // default value, may be overwritten below
|
|
||||||
for (var i = 8; i < elems.length; i += 2) {
|
|
||||||
switch (elems[i]) {
|
|
||||||
case 'raddr':
|
|
||||||
candidate['rel-addr'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'rport':
|
|
||||||
candidate['rel-port'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'generation':
|
|
||||||
candidate.generation = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'tcptype':
|
|
||||||
candidate.tcptype = elems[i + 1];
|
|
||||||
break;
|
|
||||||
default: // TODO
|
|
||||||
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
candidate.network = '1';
|
|
||||||
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
|
||||||
return candidate;
|
|
||||||
},
|
|
||||||
build_icecandidate: function (cand) {
|
|
||||||
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
|
|
||||||
line += ' ';
|
|
||||||
switch (cand.type) {
|
|
||||||
case 'srflx':
|
|
||||||
case 'prflx':
|
|
||||||
case 'relay':
|
|
||||||
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
|
|
||||||
line += 'raddr';
|
|
||||||
line += ' ';
|
|
||||||
line += cand['rel-addr'];
|
|
||||||
line += ' ';
|
|
||||||
line += 'rport';
|
|
||||||
line += ' ';
|
|
||||||
line += cand['rel-port'];
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (cand.hasOwnAttribute('tcptype')) {
|
|
||||||
line += 'tcptype';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.tcptype;
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
line += 'generation';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
|
||||||
return line;
|
|
||||||
},
|
|
||||||
parse_ssrc: function (desc) {
|
|
||||||
// proprietary mapping of a=ssrc lines
|
|
||||||
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
|
|
||||||
// and parse according to that
|
|
||||||
var lines = desc.split('\r\n'),
|
|
||||||
data = {};
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].substring(0, 7) == 'a=ssrc:') {
|
|
||||||
var idx = lines[i].indexOf(' ');
|
|
||||||
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_rtcpfb: function (line) {
|
|
||||||
var parts = line.substr(10).split(' ');
|
|
||||||
var data = {};
|
|
||||||
data.pt = parts.shift();
|
|
||||||
data.type = parts.shift();
|
|
||||||
data.params = parts;
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
parse_extmap: function (line) {
|
|
||||||
var parts = line.substr(9).split(' ');
|
|
||||||
var data = {};
|
|
||||||
data.value = parts.shift();
|
|
||||||
if (data.value.indexOf('/') != -1) {
|
|
||||||
data.direction = data.value.substr(data.value.indexOf('/') + 1);
|
|
||||||
data.value = data.value.substr(0, data.value.indexOf('/'));
|
|
||||||
} else {
|
|
||||||
data.direction = 'both';
|
|
||||||
}
|
|
||||||
data.uri = parts.shift();
|
|
||||||
data.params = parts;
|
|
||||||
return data;
|
|
||||||
},
|
|
||||||
find_line: function (haystack, needle, sessionpart) {
|
|
||||||
var lines = haystack.split('\r\n');
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].substring(0, needle.length) == needle) {
|
|
||||||
return lines[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!sessionpart) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// search session part
|
|
||||||
lines = sessionpart.split('\r\n');
|
|
||||||
for (var j = 0; j < lines.length; j++) {
|
|
||||||
if (lines[j].substring(0, needle.length) == needle) {
|
|
||||||
return lines[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
find_lines: function (haystack, needle, sessionpart) {
|
|
||||||
var lines = haystack.split('\r\n'),
|
|
||||||
needles = [];
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
if (lines[i].substring(0, needle.length) == needle)
|
|
||||||
needles.push(lines[i]);
|
|
||||||
}
|
|
||||||
if (needles.length || !sessionpart) {
|
|
||||||
return needles;
|
|
||||||
}
|
|
||||||
// search session part
|
|
||||||
lines = sessionpart.split('\r\n');
|
|
||||||
for (var j = 0; j < lines.length; j++) {
|
|
||||||
if (lines[j].substring(0, needle.length) == needle) {
|
|
||||||
needles.push(lines[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return needles;
|
|
||||||
},
|
|
||||||
candidateToJingle: function (line) {
|
|
||||||
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
|
||||||
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
|
||||||
if (line.indexOf('candidate:') === 0) {
|
|
||||||
line = 'a=' + line;
|
|
||||||
} else if (line.substring(0, 12) != 'a=candidate:') {
|
|
||||||
console.log('parseCandidate called with a line that is not a candidate line');
|
|
||||||
console.log(line);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (line.substring(line.length - 2) == '\r\n') // chomp it
|
|
||||||
line = line.substring(0, line.length - 2);
|
|
||||||
var candidate = {},
|
|
||||||
elems = line.split(' '),
|
|
||||||
i;
|
|
||||||
if (elems[6] != 'typ') {
|
|
||||||
console.log('did not find typ in the right place');
|
|
||||||
console.log(line);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
candidate.foundation = elems[0].substring(12);
|
|
||||||
candidate.component = elems[1];
|
|
||||||
candidate.protocol = elems[2].toLowerCase();
|
|
||||||
candidate.priority = elems[3];
|
|
||||||
candidate.ip = elems[4];
|
|
||||||
candidate.port = elems[5];
|
|
||||||
// elems[6] => "typ"
|
|
||||||
candidate.type = elems[7];
|
|
||||||
|
|
||||||
candidate.generation = '0'; // default, may be overwritten below
|
module.exports = SDP;
|
||||||
for (i = 8; i < elems.length; i += 2) {
|
|
||||||
switch (elems[i]) {
|
|
||||||
case 'raddr':
|
|
||||||
candidate['rel-addr'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'rport':
|
|
||||||
candidate['rel-port'] = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'generation':
|
|
||||||
candidate.generation = elems[i + 1];
|
|
||||||
break;
|
|
||||||
case 'tcptype':
|
|
||||||
candidate.tcptype = elems[i + 1];
|
|
||||||
break;
|
|
||||||
default: // TODO
|
|
||||||
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
candidate.network = '1';
|
|
||||||
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
|
||||||
return candidate;
|
|
||||||
},
|
|
||||||
candidateFromJingle: function (cand) {
|
|
||||||
var line = 'a=candidate:';
|
|
||||||
line += cand.getAttribute('foundation');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('component');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('priority');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('ip');
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('port');
|
|
||||||
line += ' ';
|
|
||||||
line += 'typ';
|
|
||||||
line += ' ' + cand.getAttribute('type');
|
|
||||||
line += ' ';
|
|
||||||
switch (cand.getAttribute('type')) {
|
|
||||||
case 'srflx':
|
|
||||||
case 'prflx':
|
|
||||||
case 'relay':
|
|
||||||
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
|
|
||||||
line += 'raddr';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('rel-addr');
|
|
||||||
line += ' ';
|
|
||||||
line += 'rport';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('rel-port');
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
|
|
||||||
line += 'tcptype';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('tcptype');
|
|
||||||
line += ' ';
|
|
||||||
}
|
|
||||||
line += 'generation';
|
|
||||||
line += ' ';
|
|
||||||
line += cand.getAttribute('generation') || '0';
|
|
||||||
return line + '\r\n';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
function SDPDiffer(mySDP, otherSDP) {
|
||||||
|
this.mySDP = mySDP;
|
||||||
|
this.otherSDP = otherSDP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
|
||||||
|
* @param otherSdp the other SDP to check ssrc with.
|
||||||
|
*/
|
||||||
|
SDPDiffer.prototype.getNewMedia = function() {
|
||||||
|
|
||||||
|
// this could be useful in Array.prototype.
|
||||||
|
function arrayEquals(array) {
|
||||||
|
// if the other array is a falsy value, return
|
||||||
|
if (!array)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// compare lengths - can save a lot of time
|
||||||
|
if (this.length != array.length)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (var i = 0, l=this.length; i < l; i++) {
|
||||||
|
// Check if we have nested arrays
|
||||||
|
if (this[i] instanceof Array && array[i] instanceof Array) {
|
||||||
|
// recurse into the nested arrays
|
||||||
|
if (!this[i].equals(array[i]))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (this[i] != array[i]) {
|
||||||
|
// Warning - two different object instances will never be equal: {x:20} != {x:20}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var myMedias = this.mySDP.getMediaSsrcMap();
|
||||||
|
var othersMedias = this.otherSDP.getMediaSsrcMap();
|
||||||
|
var newMedia = {};
|
||||||
|
Object.keys(othersMedias).forEach(function(othersMediaIdx) {
|
||||||
|
var myMedia = myMedias[othersMediaIdx];
|
||||||
|
var othersMedia = othersMedias[othersMediaIdx];
|
||||||
|
if(!myMedia && othersMedia) {
|
||||||
|
// Add whole channel
|
||||||
|
newMedia[othersMediaIdx] = othersMedia;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Look for new ssrcs accross the channel
|
||||||
|
Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
|
||||||
|
if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
|
||||||
|
// Allocate channel if we've found ssrc that doesn't exist in our channel
|
||||||
|
if(!newMedia[othersMediaIdx]){
|
||||||
|
newMedia[othersMediaIdx] = {
|
||||||
|
mediaindex: othersMedia.mediaindex,
|
||||||
|
mid: othersMedia.mid,
|
||||||
|
ssrcs: {},
|
||||||
|
ssrcGroups: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Look for new ssrc groups across the channels
|
||||||
|
othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
|
||||||
|
|
||||||
|
// try to match the other ssrc-group with an ssrc-group of ours
|
||||||
|
var matched = false;
|
||||||
|
for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
|
||||||
|
var mySsrcGroup = myMedia.ssrcGroups[i];
|
||||||
|
if (otherSsrcGroup.semantics == mySsrcGroup.semantics
|
||||||
|
&& arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
|
||||||
|
|
||||||
|
matched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!matched) {
|
||||||
|
// Allocate channel if we've found an ssrc-group that doesn't
|
||||||
|
// exist in our channel
|
||||||
|
|
||||||
|
if(!newMedia[othersMediaIdx]){
|
||||||
|
newMedia[othersMediaIdx] = {
|
||||||
|
mediaindex: othersMedia.mediaindex,
|
||||||
|
mid: othersMedia.mid,
|
||||||
|
ssrcs: {},
|
||||||
|
ssrcGroups: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return newMedia;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends SSRC update IQ.
|
||||||
|
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
|
||||||
|
* @param sid session identifier that will be put into the IQ.
|
||||||
|
* @param initiator initiator identifier.
|
||||||
|
* @param toJid destination Jid
|
||||||
|
* @param isAdd indicates if this is remove or add operation.
|
||||||
|
*/
|
||||||
|
SDPDiffer.prototype.toJingle = function(modify) {
|
||||||
|
var sdpMediaSsrcs = this.getNewMedia();
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
// FIXME: only announce video ssrcs since we mix audio and dont need
|
||||||
|
// the audio ssrcs therefore
|
||||||
|
var modified = false;
|
||||||
|
Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
|
||||||
|
modified = true;
|
||||||
|
var media = sdpMediaSsrcs[mediaindex];
|
||||||
|
modify.c('content', {name: media.mid});
|
||||||
|
|
||||||
|
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
|
||||||
|
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
|
||||||
|
// generate sources from lines
|
||||||
|
Object.keys(media.ssrcs).forEach(function(ssrcNum) {
|
||||||
|
var mediaSsrc = media.ssrcs[ssrcNum];
|
||||||
|
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
|
||||||
|
modify.attrs({ssrc: mediaSsrc.ssrc});
|
||||||
|
// iterate over ssrc lines
|
||||||
|
mediaSsrc.lines.forEach(function (line) {
|
||||||
|
var idx = line.indexOf(' ');
|
||||||
|
var kv = line.substr(idx + 1);
|
||||||
|
modify.c('parameter');
|
||||||
|
if (kv.indexOf(':') == -1) {
|
||||||
|
modify.attrs({ name: kv });
|
||||||
|
} else {
|
||||||
|
modify.attrs({ name: kv.split(':', 2)[0] });
|
||||||
|
modify.attrs({ value: kv.split(':', 2)[1] });
|
||||||
|
}
|
||||||
|
modify.up(); // end of parameter
|
||||||
|
});
|
||||||
|
modify.up(); // end of source
|
||||||
|
});
|
||||||
|
|
||||||
|
// generate source groups from lines
|
||||||
|
media.ssrcGroups.forEach(function(ssrcGroup) {
|
||||||
|
if (ssrcGroup.ssrcs.length != 0) {
|
||||||
|
|
||||||
|
modify.c('ssrc-group', {
|
||||||
|
semantics: ssrcGroup.semantics,
|
||||||
|
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
|
||||||
|
});
|
||||||
|
|
||||||
|
ssrcGroup.ssrcs.forEach(function (ssrc) {
|
||||||
|
modify.c('source', { ssrc: ssrc })
|
||||||
|
.up(); // end of source
|
||||||
|
});
|
||||||
|
modify.up(); // end of ssrc-group
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
modify.up(); // end of description
|
||||||
|
modify.up(); // end of content
|
||||||
|
});
|
||||||
|
|
||||||
|
return modified;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = SDPDiffer;
|
|
@ -0,0 +1,349 @@
|
||||||
|
SDPUtil = {
|
||||||
|
iceparams: function (mediadesc, sessiondesc) {
|
||||||
|
var data = null;
|
||||||
|
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
|
||||||
|
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
|
||||||
|
data = {
|
||||||
|
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
|
||||||
|
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_iceufrag: function (line) {
|
||||||
|
return line.substring(12);
|
||||||
|
},
|
||||||
|
build_iceufrag: function (frag) {
|
||||||
|
return 'a=ice-ufrag:' + frag;
|
||||||
|
},
|
||||||
|
parse_icepwd: function (line) {
|
||||||
|
return line.substring(10);
|
||||||
|
},
|
||||||
|
build_icepwd: function (pwd) {
|
||||||
|
return 'a=ice-pwd:' + pwd;
|
||||||
|
},
|
||||||
|
parse_mid: function (line) {
|
||||||
|
return line.substring(6);
|
||||||
|
},
|
||||||
|
parse_mline: function (line) {
|
||||||
|
var parts = line.substring(2).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.media = parts.shift();
|
||||||
|
data.port = parts.shift();
|
||||||
|
data.proto = parts.shift();
|
||||||
|
if (parts[parts.length - 1] === '') { // trailing whitespace
|
||||||
|
parts.pop();
|
||||||
|
}
|
||||||
|
data.fmt = parts;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
build_mline: function (mline) {
|
||||||
|
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
|
||||||
|
},
|
||||||
|
parse_rtpmap: function (line) {
|
||||||
|
var parts = line.substring(9).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.id = parts.shift();
|
||||||
|
parts = parts[0].split('/');
|
||||||
|
data.name = parts.shift();
|
||||||
|
data.clockrate = parts.shift();
|
||||||
|
data.channels = parts.length ? parts.shift() : '1';
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
|
||||||
|
* @param line eg. "a=sctpmap:5000 webrtc-datachannel"
|
||||||
|
* @returns [SCTP port number, protocol, streams]
|
||||||
|
*/
|
||||||
|
parse_sctpmap: function (line)
|
||||||
|
{
|
||||||
|
var parts = line.substring(10).split(' ');
|
||||||
|
var sctpPort = parts[0];
|
||||||
|
var protocol = parts[1];
|
||||||
|
// Stream count is optional
|
||||||
|
var streamCount = parts.length > 2 ? parts[2] : null;
|
||||||
|
return [sctpPort, protocol, streamCount];// SCTP port
|
||||||
|
},
|
||||||
|
build_rtpmap: function (el) {
|
||||||
|
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
|
||||||
|
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
|
||||||
|
line += '/' + el.getAttribute('channels');
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
parse_crypto: function (line) {
|
||||||
|
var parts = line.substring(9).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.tag = parts.shift();
|
||||||
|
data['crypto-suite'] = parts.shift();
|
||||||
|
data['key-params'] = parts.shift();
|
||||||
|
if (parts.length) {
|
||||||
|
data['session-params'] = parts.join(' ');
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_fingerprint: function (line) { // RFC 4572
|
||||||
|
var parts = line.substring(14).split(' '),
|
||||||
|
data = {};
|
||||||
|
data.hash = parts.shift();
|
||||||
|
data.fingerprint = parts.shift();
|
||||||
|
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_fmtp: function (line) {
|
||||||
|
var parts = line.split(' '),
|
||||||
|
i, key, value,
|
||||||
|
data = [];
|
||||||
|
parts.shift();
|
||||||
|
parts = parts.join(' ').split(';');
|
||||||
|
for (i = 0; i < parts.length; i++) {
|
||||||
|
key = parts[i].split('=')[0];
|
||||||
|
while (key.length && key[0] == ' ') {
|
||||||
|
key = key.substring(1);
|
||||||
|
}
|
||||||
|
value = parts[i].split('=')[1];
|
||||||
|
if (key && value) {
|
||||||
|
data.push({name: key, value: value});
|
||||||
|
} else if (key) {
|
||||||
|
// rfc 4733 (DTMF) style stuff
|
||||||
|
data.push({name: '', value: key});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_icecandidate: function (line) {
|
||||||
|
var candidate = {},
|
||||||
|
elems = line.split(' ');
|
||||||
|
candidate.foundation = elems[0].substring(12);
|
||||||
|
candidate.component = elems[1];
|
||||||
|
candidate.protocol = elems[2].toLowerCase();
|
||||||
|
candidate.priority = elems[3];
|
||||||
|
candidate.ip = elems[4];
|
||||||
|
candidate.port = elems[5];
|
||||||
|
// elems[6] => "typ"
|
||||||
|
candidate.type = elems[7];
|
||||||
|
candidate.generation = 0; // default value, may be overwritten below
|
||||||
|
for (var i = 8; i < elems.length; i += 2) {
|
||||||
|
switch (elems[i]) {
|
||||||
|
case 'raddr':
|
||||||
|
candidate['rel-addr'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'rport':
|
||||||
|
candidate['rel-port'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'generation':
|
||||||
|
candidate.generation = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'tcptype':
|
||||||
|
candidate.tcptype = elems[i + 1];
|
||||||
|
break;
|
||||||
|
default: // TODO
|
||||||
|
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidate.network = '1';
|
||||||
|
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
||||||
|
return candidate;
|
||||||
|
},
|
||||||
|
build_icecandidate: function (cand) {
|
||||||
|
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
|
||||||
|
line += ' ';
|
||||||
|
switch (cand.type) {
|
||||||
|
case 'srflx':
|
||||||
|
case 'prflx':
|
||||||
|
case 'relay':
|
||||||
|
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
|
||||||
|
line += 'raddr';
|
||||||
|
line += ' ';
|
||||||
|
line += cand['rel-addr'];
|
||||||
|
line += ' ';
|
||||||
|
line += 'rport';
|
||||||
|
line += ' ';
|
||||||
|
line += cand['rel-port'];
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cand.hasOwnAttribute('tcptype')) {
|
||||||
|
line += 'tcptype';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.tcptype;
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
line += 'generation';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
||||||
|
return line;
|
||||||
|
},
|
||||||
|
parse_ssrc: function (desc) {
|
||||||
|
// proprietary mapping of a=ssrc lines
|
||||||
|
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
|
||||||
|
// and parse according to that
|
||||||
|
var lines = desc.split('\r\n'),
|
||||||
|
data = {};
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].substring(0, 7) == 'a=ssrc:') {
|
||||||
|
var idx = lines[i].indexOf(' ');
|
||||||
|
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_rtcpfb: function (line) {
|
||||||
|
var parts = line.substr(10).split(' ');
|
||||||
|
var data = {};
|
||||||
|
data.pt = parts.shift();
|
||||||
|
data.type = parts.shift();
|
||||||
|
data.params = parts;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
parse_extmap: function (line) {
|
||||||
|
var parts = line.substr(9).split(' ');
|
||||||
|
var data = {};
|
||||||
|
data.value = parts.shift();
|
||||||
|
if (data.value.indexOf('/') != -1) {
|
||||||
|
data.direction = data.value.substr(data.value.indexOf('/') + 1);
|
||||||
|
data.value = data.value.substr(0, data.value.indexOf('/'));
|
||||||
|
} else {
|
||||||
|
data.direction = 'both';
|
||||||
|
}
|
||||||
|
data.uri = parts.shift();
|
||||||
|
data.params = parts;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
find_line: function (haystack, needle, sessionpart) {
|
||||||
|
var lines = haystack.split('\r\n');
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].substring(0, needle.length) == needle) {
|
||||||
|
return lines[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!sessionpart) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// search session part
|
||||||
|
lines = sessionpart.split('\r\n');
|
||||||
|
for (var j = 0; j < lines.length; j++) {
|
||||||
|
if (lines[j].substring(0, needle.length) == needle) {
|
||||||
|
return lines[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
find_lines: function (haystack, needle, sessionpart) {
|
||||||
|
var lines = haystack.split('\r\n'),
|
||||||
|
needles = [];
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].substring(0, needle.length) == needle)
|
||||||
|
needles.push(lines[i]);
|
||||||
|
}
|
||||||
|
if (needles.length || !sessionpart) {
|
||||||
|
return needles;
|
||||||
|
}
|
||||||
|
// search session part
|
||||||
|
lines = sessionpart.split('\r\n');
|
||||||
|
for (var j = 0; j < lines.length; j++) {
|
||||||
|
if (lines[j].substring(0, needle.length) == needle) {
|
||||||
|
needles.push(lines[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return needles;
|
||||||
|
},
|
||||||
|
candidateToJingle: function (line) {
|
||||||
|
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
||||||
|
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
||||||
|
if (line.indexOf('candidate:') === 0) {
|
||||||
|
line = 'a=' + line;
|
||||||
|
} else if (line.substring(0, 12) != 'a=candidate:') {
|
||||||
|
console.log('parseCandidate called with a line that is not a candidate line');
|
||||||
|
console.log(line);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (line.substring(line.length - 2) == '\r\n') // chomp it
|
||||||
|
line = line.substring(0, line.length - 2);
|
||||||
|
var candidate = {},
|
||||||
|
elems = line.split(' '),
|
||||||
|
i;
|
||||||
|
if (elems[6] != 'typ') {
|
||||||
|
console.log('did not find typ in the right place');
|
||||||
|
console.log(line);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
candidate.foundation = elems[0].substring(12);
|
||||||
|
candidate.component = elems[1];
|
||||||
|
candidate.protocol = elems[2].toLowerCase();
|
||||||
|
candidate.priority = elems[3];
|
||||||
|
candidate.ip = elems[4];
|
||||||
|
candidate.port = elems[5];
|
||||||
|
// elems[6] => "typ"
|
||||||
|
candidate.type = elems[7];
|
||||||
|
|
||||||
|
candidate.generation = '0'; // default, may be overwritten below
|
||||||
|
for (i = 8; i < elems.length; i += 2) {
|
||||||
|
switch (elems[i]) {
|
||||||
|
case 'raddr':
|
||||||
|
candidate['rel-addr'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'rport':
|
||||||
|
candidate['rel-port'] = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'generation':
|
||||||
|
candidate.generation = elems[i + 1];
|
||||||
|
break;
|
||||||
|
case 'tcptype':
|
||||||
|
candidate.tcptype = elems[i + 1];
|
||||||
|
break;
|
||||||
|
default: // TODO
|
||||||
|
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
candidate.network = '1';
|
||||||
|
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
||||||
|
return candidate;
|
||||||
|
},
|
||||||
|
candidateFromJingle: function (cand) {
|
||||||
|
var line = 'a=candidate:';
|
||||||
|
line += cand.getAttribute('foundation');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('component');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('priority');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('ip');
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('port');
|
||||||
|
line += ' ';
|
||||||
|
line += 'typ';
|
||||||
|
line += ' ' + cand.getAttribute('type');
|
||||||
|
line += ' ';
|
||||||
|
switch (cand.getAttribute('type')) {
|
||||||
|
case 'srflx':
|
||||||
|
case 'prflx':
|
||||||
|
case 'relay':
|
||||||
|
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
|
||||||
|
line += 'raddr';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('rel-addr');
|
||||||
|
line += ' ';
|
||||||
|
line += 'rport';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('rel-port');
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
|
||||||
|
line += 'tcptype';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('tcptype');
|
||||||
|
line += ' ';
|
||||||
|
}
|
||||||
|
line += 'generation';
|
||||||
|
line += ' ';
|
||||||
|
line += cand.getAttribute('generation') || '0';
|
||||||
|
return line + '\r\n';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
module.exports = SDPUtil;
|
|
@ -262,3 +262,5 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
module.exports = TraceablePeerConnection;
|
||||||
|
|
|
@ -1,47 +1,53 @@
|
||||||
/* global $, $iq, config, connection, Etherpad, hangUp, messageHandler,
|
/* global $, $iq, config, connection, UI, messageHandler,
|
||||||
roomName, sessionTerminated, Strophe, Util */
|
roomName, sessionTerminated, Strophe, Util */
|
||||||
/**
|
/**
|
||||||
* Contains logic responsible for enabling/disabling functionality available
|
* Contains logic responsible for enabling/disabling functionality available
|
||||||
* only to moderator users.
|
* only to moderator users.
|
||||||
*/
|
*/
|
||||||
var Moderator = (function (my) {
|
var connection = null;
|
||||||
|
var focusUserJid;
|
||||||
|
var getNextTimeout = Util.createExpBackoffTimer(1000);
|
||||||
|
var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
|
||||||
|
// External authentication stuff
|
||||||
|
var externalAuthEnabled = false;
|
||||||
|
// Sip gateway can be enabled by configuring Jigasi host in config.js or
|
||||||
|
// it will be enabled automatically if focus detects the component through
|
||||||
|
// service discovery.
|
||||||
|
var sipGatewayEnabled = config.hosts.call_control !== undefined;
|
||||||
|
|
||||||
var focusUserJid;
|
var Moderator = {
|
||||||
var getNextTimeout = Util.createExpBackoffTimer(1000);
|
isModerator: function () {
|
||||||
var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
|
|
||||||
// External authentication stuff
|
|
||||||
var externalAuthEnabled = false;
|
|
||||||
// Sip gateway can be enabled by configuring Jigasi host in config.js or
|
|
||||||
// it will be enabled automatically if focus detects the component through
|
|
||||||
// service discovery.
|
|
||||||
var sipGatewayEnabled = config.hosts.call_control !== undefined;
|
|
||||||
|
|
||||||
my.isModerator = function () {
|
|
||||||
return connection && connection.emuc.isModerator();
|
return connection && connection.emuc.isModerator();
|
||||||
};
|
},
|
||||||
|
|
||||||
my.isPeerModerator = function (peerJid) {
|
isPeerModerator: function (peerJid) {
|
||||||
return connection && connection.emuc.getMemberRole(peerJid) === 'moderator';
|
return connection &&
|
||||||
};
|
connection.emuc.getMemberRole(peerJid) === 'moderator';
|
||||||
|
},
|
||||||
|
|
||||||
my.isExternalAuthEnabled = function () {
|
isExternalAuthEnabled: function () {
|
||||||
return externalAuthEnabled;
|
return externalAuthEnabled;
|
||||||
};
|
},
|
||||||
|
|
||||||
my.isSipGatewayEnabled = function () {
|
isSipGatewayEnabled: function () {
|
||||||
return sipGatewayEnabled;
|
return sipGatewayEnabled;
|
||||||
};
|
},
|
||||||
|
|
||||||
my.init = function () {
|
setConnection: function (con) {
|
||||||
Moderator.onLocalRoleChange = function (from, member, pres) {
|
connection = con;
|
||||||
|
},
|
||||||
|
|
||||||
|
init: function (xmpp) {
|
||||||
|
this.xmppService = xmpp;
|
||||||
|
this.onLocalRoleChange = function (from, member, pres) {
|
||||||
UI.onModeratorStatusChanged(Moderator.isModerator());
|
UI.onModeratorStatusChanged(Moderator.isModerator());
|
||||||
};
|
};
|
||||||
};
|
},
|
||||||
|
|
||||||
my.onMucLeft = function (jid) {
|
onMucLeft: function (jid) {
|
||||||
console.info("Someone left is it focus ? " + jid);
|
console.info("Someone left is it focus ? " + jid);
|
||||||
var resource = Strophe.getResourceFromJid(jid);
|
var resource = Strophe.getResourceFromJid(jid);
|
||||||
if (resource === 'focus' && !sessionTerminated) {
|
if (resource === 'focus' && !this.xmppService.sessionTerminated) {
|
||||||
console.info(
|
console.info(
|
||||||
"Focus has left the room - leaving conference");
|
"Focus has left the room - leaving conference");
|
||||||
//hangUp();
|
//hangUp();
|
||||||
|
@ -49,20 +55,20 @@ var Moderator = (function (my) {
|
||||||
// FIXME: show some message before reload
|
// FIXME: show some message before reload
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
my.setFocusUserJid = function (focusJid) {
|
setFocusUserJid: function (focusJid) {
|
||||||
if (!focusUserJid) {
|
if (!focusUserJid) {
|
||||||
focusUserJid = focusJid;
|
focusUserJid = focusJid;
|
||||||
console.info("Focus jid set to: " + focusUserJid);
|
console.info("Focus jid set to: " + focusUserJid);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
|
||||||
my.getFocusUserJid = function () {
|
getFocusUserJid: function () {
|
||||||
return focusUserJid;
|
return focusUserJid;
|
||||||
};
|
},
|
||||||
|
|
||||||
my.getFocusComponent = function () {
|
getFocusComponent: function () {
|
||||||
// Get focus component address
|
// Get focus component address
|
||||||
var focusComponent = config.hosts.focus;
|
var focusComponent = config.hosts.focus;
|
||||||
// If not specified use default: 'focus.domain'
|
// If not specified use default: 'focus.domain'
|
||||||
|
@ -70,70 +76,64 @@ var Moderator = (function (my) {
|
||||||
focusComponent = 'focus.' + config.hosts.domain;
|
focusComponent = 'focus.' + config.hosts.domain;
|
||||||
}
|
}
|
||||||
return focusComponent;
|
return focusComponent;
|
||||||
};
|
},
|
||||||
|
|
||||||
my.createConferenceIq = function () {
|
createConferenceIq: function (roomName) {
|
||||||
// Generate create conference IQ
|
// Generate create conference IQ
|
||||||
var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
|
var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
|
||||||
elem.c('conference', {
|
elem.c('conference', {
|
||||||
xmlns: 'http://jitsi.org/protocol/focus',
|
xmlns: 'http://jitsi.org/protocol/focus',
|
||||||
room: roomName
|
room: roomName
|
||||||
});
|
});
|
||||||
if (config.hosts.bridge !== undefined)
|
if (config.hosts.bridge !== undefined) {
|
||||||
{
|
|
||||||
elem.c(
|
elem.c(
|
||||||
'property',
|
'property',
|
||||||
{ name: 'bridge', value: config.hosts.bridge})
|
{ name: 'bridge', value: config.hosts.bridge})
|
||||||
.up();
|
.up();
|
||||||
}
|
}
|
||||||
// Tell the focus we have Jigasi configured
|
// Tell the focus we have Jigasi configured
|
||||||
if (config.hosts.call_control !== undefined)
|
if (config.hosts.call_control !== undefined) {
|
||||||
{
|
|
||||||
elem.c(
|
elem.c(
|
||||||
'property',
|
'property',
|
||||||
{ name: 'call_control', value: config.hosts.call_control})
|
{ name: 'call_control', value: config.hosts.call_control})
|
||||||
.up();
|
.up();
|
||||||
}
|
}
|
||||||
if (config.channelLastN !== undefined)
|
if (config.channelLastN !== undefined) {
|
||||||
{
|
|
||||||
elem.c(
|
elem.c(
|
||||||
'property',
|
'property',
|
||||||
{ name: 'channelLastN', value: config.channelLastN})
|
{ name: 'channelLastN', value: config.channelLastN})
|
||||||
.up();
|
.up();
|
||||||
}
|
}
|
||||||
if (config.adaptiveLastN !== undefined)
|
if (config.adaptiveLastN !== undefined) {
|
||||||
{
|
|
||||||
elem.c(
|
elem.c(
|
||||||
'property',
|
'property',
|
||||||
{ name: 'adaptiveLastN', value: config.adaptiveLastN})
|
{ name: 'adaptiveLastN', value: config.adaptiveLastN})
|
||||||
.up();
|
.up();
|
||||||
}
|
}
|
||||||
if (config.adaptiveSimulcast !== undefined)
|
if (config.adaptiveSimulcast !== undefined) {
|
||||||
{
|
|
||||||
elem.c(
|
elem.c(
|
||||||
'property',
|
'property',
|
||||||
{ name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
|
{ name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
|
||||||
.up();
|
.up();
|
||||||
}
|
}
|
||||||
if (config.openSctp !== undefined)
|
if (config.openSctp !== undefined) {
|
||||||
{
|
|
||||||
elem.c(
|
elem.c(
|
||||||
'property',
|
'property',
|
||||||
{ name: 'openSctp', value: config.openSctp})
|
{ name: 'openSctp', value: config.openSctp})
|
||||||
.up();
|
.up();
|
||||||
}
|
}
|
||||||
if (config.enableFirefoxSupport !== undefined)
|
if (config.enableFirefoxSupport !== undefined) {
|
||||||
{
|
|
||||||
elem.c(
|
elem.c(
|
||||||
'property',
|
'property',
|
||||||
{ name: 'enableFirefoxHacks', value: config.enableFirefoxSupport})
|
{ name: 'enableFirefoxHacks',
|
||||||
|
value: config.enableFirefoxSupport})
|
||||||
.up();
|
.up();
|
||||||
}
|
}
|
||||||
elem.up();
|
elem.up();
|
||||||
return elem;
|
return elem;
|
||||||
};
|
},
|
||||||
|
|
||||||
my.parseConfigOptions = function (resultIq) {
|
parseConfigOptions: function (resultIq) {
|
||||||
|
|
||||||
Moderator.setFocusUserJid(
|
Moderator.setFocusUserJid(
|
||||||
$(resultIq).find('conference').attr('focusjid'));
|
$(resultIq).find('conference').attr('focusjid'));
|
||||||
|
@ -154,15 +154,15 @@ var Moderator = (function (my) {
|
||||||
}
|
}
|
||||||
|
|
||||||
console.info("Sip gateway enabled: " + sipGatewayEnabled);
|
console.info("Sip gateway enabled: " + sipGatewayEnabled);
|
||||||
};
|
},
|
||||||
|
|
||||||
// FIXME: we need to show the fact that we're waiting for the focus
|
// FIXME: we need to show the fact that we're waiting for the focus
|
||||||
// to the user(or that focus is not available)
|
// to the user(or that focus is not available)
|
||||||
my.allocateConferenceFocus = function (roomName, callback) {
|
allocateConferenceFocus: function (roomName, callback) {
|
||||||
// Try to use focus user JID from the config
|
// Try to use focus user JID from the config
|
||||||
Moderator.setFocusUserJid(config.focusUserJid);
|
Moderator.setFocusUserJid(config.focusUserJid);
|
||||||
// Send create conference IQ
|
// Send create conference IQ
|
||||||
var iq = Moderator.createConferenceIq();
|
var iq = Moderator.createConferenceIq(roomName);
|
||||||
connection.sendIQ(
|
connection.sendIQ(
|
||||||
iq,
|
iq,
|
||||||
function (result) {
|
function (result) {
|
||||||
|
@ -190,7 +190,9 @@ var Moderator = (function (my) {
|
||||||
// Not authorized to create new room
|
// Not authorized to create new room
|
||||||
if ($(error).find('>error>not-authorized').length) {
|
if ($(error).find('>error>not-authorized').length) {
|
||||||
console.warn("Unauthorized to start the conference");
|
console.warn("Unauthorized to start the conference");
|
||||||
UI.onAuthenticationRequired();
|
UI.onAuthenticationRequired(function () {
|
||||||
|
Moderator.allocateConferenceFocus(roomName, callback);
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var waitMs = getNextErrorTimeout();
|
var waitMs = getNextErrorTimeout();
|
||||||
|
@ -199,7 +201,8 @@ var Moderator = (function (my) {
|
||||||
UI.messageHandler.notify(
|
UI.messageHandler.notify(
|
||||||
'Conference focus', 'disconnected',
|
'Conference focus', 'disconnected',
|
||||||
Moderator.getFocusComponent() +
|
Moderator.getFocusComponent() +
|
||||||
' not available - retry in ' + (waitMs / 1000) + ' sec');
|
' not available - retry in ' +
|
||||||
|
(waitMs / 1000) + ' sec');
|
||||||
// Reset response timeout
|
// Reset response timeout
|
||||||
getNextTimeout(true);
|
getNextTimeout(true);
|
||||||
window.setTimeout(
|
window.setTimeout(
|
||||||
|
@ -208,9 +211,9 @@ var Moderator = (function (my) {
|
||||||
}, waitMs);
|
}, waitMs);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
},
|
||||||
|
|
||||||
my.getAuthUrl = function (urlCallback) {
|
getAuthUrl: function (roomName, urlCallback) {
|
||||||
var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
|
var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
|
||||||
iq.c('auth-url', {
|
iq.c('auth-url', {
|
||||||
xmlns: 'http://jitsi.org/protocol/focus',
|
xmlns: 'http://jitsi.org/protocol/focus',
|
||||||
|
@ -232,10 +235,10 @@ var Moderator = (function (my) {
|
||||||
console.error("Get auth url error", error);
|
console.error("Get auth url error", error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return my;
|
module.exports = Moderator;
|
||||||
}(Moderator || {}));
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,152 @@
|
||||||
|
/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
|
||||||
|
Toolbar, Util */
|
||||||
|
var Moderator = require("./moderator");
|
||||||
|
|
||||||
|
|
||||||
|
var recordingToken = null;
|
||||||
|
var recordingEnabled;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether to use a jirecon component for recording, or use the videobridge
|
||||||
|
* through COLIBRI.
|
||||||
|
*/
|
||||||
|
var useJirecon = (typeof config.hosts.jirecon != "undefined");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the jirecon recording session. Jirecon generates it when we
|
||||||
|
* initially start recording, and it needs to be used in subsequent requests
|
||||||
|
* to jirecon.
|
||||||
|
*/
|
||||||
|
var jireconRid = null;
|
||||||
|
|
||||||
|
function setRecordingToken(token) {
|
||||||
|
recordingToken = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRecording(state, token, callback) {
|
||||||
|
if (useJirecon){
|
||||||
|
this.setRecordingJirecon(state, token, callback);
|
||||||
|
} else {
|
||||||
|
this.setRecordingColibri(state, token, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setRecordingJirecon(state, token, callback) {
|
||||||
|
if (state == recordingEnabled){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var iq = $iq({to: config.hosts.jirecon, type: 'set'})
|
||||||
|
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
|
||||||
|
action: state ? 'start' : 'stop',
|
||||||
|
mucjid: connection.emuc.roomjid});
|
||||||
|
if (!state){
|
||||||
|
iq.attrs({rid: jireconRid});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Start recording');
|
||||||
|
|
||||||
|
connection.sendIQ(
|
||||||
|
iq,
|
||||||
|
function (result) {
|
||||||
|
// TODO wait for an IQ with the real status, since this is
|
||||||
|
// provisional?
|
||||||
|
jireconRid = $(result).find('recording').attr('rid');
|
||||||
|
console.log('Recording ' + (state ? 'started' : 'stopped') +
|
||||||
|
'(jirecon)' + result);
|
||||||
|
recordingEnabled = state;
|
||||||
|
if (!state){
|
||||||
|
jireconRid = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(state);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('Failed to start recording, error: ', error);
|
||||||
|
callback(recordingEnabled);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sends a COLIBRI message which enables or disables (according to 'state')
|
||||||
|
// the recording on the bridge. Waits for the result IQ and calls 'callback'
|
||||||
|
// with the new recording state, according to the IQ.
|
||||||
|
function setRecordingColibri(state, token, callback) {
|
||||||
|
var elem = $iq({to: focusMucJid, type: 'set'});
|
||||||
|
elem.c('conference', {
|
||||||
|
xmlns: 'http://jitsi.org/protocol/colibri'
|
||||||
|
});
|
||||||
|
elem.c('recording', {state: state, token: token});
|
||||||
|
|
||||||
|
connection.sendIQ(elem,
|
||||||
|
function (result) {
|
||||||
|
console.log('Set recording "', state, '". Result:', result);
|
||||||
|
var recordingElem = $(result).find('>conference>recording');
|
||||||
|
var newState = ('true' === recordingElem.attr('state'));
|
||||||
|
|
||||||
|
recordingEnabled = newState;
|
||||||
|
callback(newState);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.warn(error);
|
||||||
|
callback(recordingEnabled);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var Recording = {
|
||||||
|
toggleRecording: function (tokenEmptyCallback,
|
||||||
|
startingCallback, startedCallback) {
|
||||||
|
if (!Moderator.isModerator()) {
|
||||||
|
console.log(
|
||||||
|
'non-focus, or conference not yet organized:' +
|
||||||
|
' not enabling recording');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Jirecon does not (currently) support a token.
|
||||||
|
if (!recordingToken && !useJirecon) {
|
||||||
|
tokenEmptyCallback(function (value) {
|
||||||
|
setRecordingToken(value);
|
||||||
|
this.toggleRecording();
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var oldState = recordingEnabled;
|
||||||
|
startingCallback(!oldState);
|
||||||
|
setRecording(!oldState,
|
||||||
|
recordingToken,
|
||||||
|
function (state) {
|
||||||
|
console.log("New recording state: ", state);
|
||||||
|
if (state === oldState) {
|
||||||
|
// FIXME: new focus:
|
||||||
|
// this will not work when moderator changes
|
||||||
|
// during active session. Then it will assume that
|
||||||
|
// recording status has changed to true, but it might have
|
||||||
|
// been already true(and we only received actual status from
|
||||||
|
// the focus).
|
||||||
|
//
|
||||||
|
// SO we start with status null, so that it is initialized
|
||||||
|
// here and will fail only after second click, so if invalid
|
||||||
|
// token was used we have to press the button twice before
|
||||||
|
// current status will be fetched and token will be reset.
|
||||||
|
//
|
||||||
|
// Reliable way would be to return authentication error.
|
||||||
|
// Or status update when moderator connects.
|
||||||
|
// Or we have to stop recording session when current
|
||||||
|
// moderator leaves the room.
|
||||||
|
|
||||||
|
// Failed to change, reset the token because it might
|
||||||
|
// have been wrong
|
||||||
|
setRecordingToken(null);
|
||||||
|
}
|
||||||
|
startedCallback(state);
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Recording;
|
|
@ -0,0 +1,607 @@
|
||||||
|
/* jshint -W117 */
|
||||||
|
/* a simple MUC connection plugin
|
||||||
|
* can only handle a single MUC room
|
||||||
|
*/
|
||||||
|
|
||||||
|
var bridgeIsDown = false;
|
||||||
|
|
||||||
|
var Moderator = require("./moderator");
|
||||||
|
|
||||||
|
module.exports = function(XMPP, eventEmitter) {
|
||||||
|
Strophe.addConnectionPlugin('emuc', {
|
||||||
|
connection: null,
|
||||||
|
roomjid: null,
|
||||||
|
myroomjid: null,
|
||||||
|
members: {},
|
||||||
|
list_members: [], // so we can elect a new focus
|
||||||
|
presMap: {},
|
||||||
|
preziMap: {},
|
||||||
|
joined: false,
|
||||||
|
isOwner: false,
|
||||||
|
role: null,
|
||||||
|
init: function (conn) {
|
||||||
|
this.connection = conn;
|
||||||
|
},
|
||||||
|
initPresenceMap: function (myroomjid) {
|
||||||
|
this.presMap['to'] = myroomjid;
|
||||||
|
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
|
||||||
|
},
|
||||||
|
doJoin: function (jid, password) {
|
||||||
|
this.myroomjid = jid;
|
||||||
|
|
||||||
|
console.info("Joined MUC as " + this.myroomjid);
|
||||||
|
|
||||||
|
this.initPresenceMap(this.myroomjid);
|
||||||
|
|
||||||
|
if (!this.roomjid) {
|
||||||
|
this.roomjid = Strophe.getBareJidFromJid(jid);
|
||||||
|
// add handlers (just once)
|
||||||
|
this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
|
||||||
|
this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
|
||||||
|
this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
|
||||||
|
this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
|
||||||
|
}
|
||||||
|
if (password !== undefined) {
|
||||||
|
this.presMap['password'] = password;
|
||||||
|
}
|
||||||
|
this.sendPresence();
|
||||||
|
},
|
||||||
|
doLeave: function () {
|
||||||
|
console.log("do leave", this.myroomjid);
|
||||||
|
var pres = $pres({to: this.myroomjid, type: 'unavailable' });
|
||||||
|
this.presMap.length = 0;
|
||||||
|
this.connection.send(pres);
|
||||||
|
},
|
||||||
|
createNonAnonymousRoom: function () {
|
||||||
|
// http://xmpp.org/extensions/xep-0045.html#createroom-reserved
|
||||||
|
|
||||||
|
var getForm = $iq({type: 'get', to: this.roomjid})
|
||||||
|
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
|
||||||
|
.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||||
|
|
||||||
|
this.connection.sendIQ(getForm, function (form) {
|
||||||
|
|
||||||
|
if (!$(form).find(
|
||||||
|
'>query>x[xmlns="jabber:x:data"]' +
|
||||||
|
'>field[var="muc#roomconfig_whois"]').length) {
|
||||||
|
|
||||||
|
console.error('non-anonymous rooms not supported');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var formSubmit = $iq({to: this.roomjid, type: 'set'})
|
||||||
|
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
||||||
|
|
||||||
|
formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||||
|
|
||||||
|
formSubmit.c('field', {'var': 'FORM_TYPE'})
|
||||||
|
.c('value')
|
||||||
|
.t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
||||||
|
|
||||||
|
formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
|
||||||
|
.c('value').t('anyone').up().up();
|
||||||
|
|
||||||
|
this.connection.sendIQ(formSubmit);
|
||||||
|
|
||||||
|
}, function (error) {
|
||||||
|
console.error("Error getting room configuration form");
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onPresence: function (pres) {
|
||||||
|
var from = pres.getAttribute('from');
|
||||||
|
|
||||||
|
// What is this for? A workaround for something?
|
||||||
|
if (pres.getAttribute('type')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse etherpad tag.
|
||||||
|
var etherpad = $(pres).find('>etherpad');
|
||||||
|
if (etherpad.length) {
|
||||||
|
if (config.etherpad_base && !Moderator.isModerator()) {
|
||||||
|
UI.initEtherpad(etherpad.text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse prezi tag.
|
||||||
|
var presentation = $(pres).find('>prezi');
|
||||||
|
if (presentation.length) {
|
||||||
|
var url = presentation.attr('url');
|
||||||
|
var current = presentation.find('>current').text();
|
||||||
|
|
||||||
|
console.log('presentation info received from', from, url);
|
||||||
|
|
||||||
|
if (this.preziMap[from] == null) {
|
||||||
|
this.preziMap[from] = url;
|
||||||
|
|
||||||
|
$(document).trigger('presentationadded.muc', [from, url, current]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$(document).trigger('gotoslide.muc', [from, url, current]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (this.preziMap[from] != null) {
|
||||||
|
var url = this.preziMap[from];
|
||||||
|
delete this.preziMap[from];
|
||||||
|
$(document).trigger('presentationremoved.muc', [from, url]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse audio info tag.
|
||||||
|
var audioMuted = $(pres).find('>audiomuted');
|
||||||
|
if (audioMuted.length) {
|
||||||
|
$(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse video info tag.
|
||||||
|
var videoMuted = $(pres).find('>videomuted');
|
||||||
|
if (videoMuted.length) {
|
||||||
|
$(document).trigger('videomuted.muc', [from, videoMuted.text()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats = $(pres).find('>stats');
|
||||||
|
if (stats.length) {
|
||||||
|
var statsObj = {};
|
||||||
|
Strophe.forEachChild(stats[0], "stat", function (el) {
|
||||||
|
statsObj[el.getAttribute("name")] = el.getAttribute("value");
|
||||||
|
});
|
||||||
|
connectionquality.updateRemoteStats(from, statsObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse status.
|
||||||
|
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
|
||||||
|
this.isOwner = true;
|
||||||
|
this.createNonAnonymousRoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse roles.
|
||||||
|
var member = {};
|
||||||
|
member.show = $(pres).find('>show').text();
|
||||||
|
member.status = $(pres).find('>status').text();
|
||||||
|
var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
|
||||||
|
member.affiliation = tmp.attr('affiliation');
|
||||||
|
member.role = tmp.attr('role');
|
||||||
|
|
||||||
|
// Focus recognition
|
||||||
|
member.jid = tmp.attr('jid');
|
||||||
|
member.isFocus = false;
|
||||||
|
if (member.jid
|
||||||
|
&& member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
|
||||||
|
member.isFocus = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
|
||||||
|
member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
|
||||||
|
|
||||||
|
if (from == this.myroomjid) {
|
||||||
|
if (member.affiliation == 'owner') this.isOwner = true;
|
||||||
|
if (this.role !== member.role) {
|
||||||
|
this.role = member.role;
|
||||||
|
if (Moderator.onLocalRoleChange)
|
||||||
|
Moderator.onLocalRoleChange(from, member, pres);
|
||||||
|
UI.onLocalRoleChange(from, member, pres);
|
||||||
|
}
|
||||||
|
if (!this.joined) {
|
||||||
|
this.joined = true;
|
||||||
|
eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
|
||||||
|
this.list_members.push(from);
|
||||||
|
}
|
||||||
|
} else if (this.members[from] === undefined) {
|
||||||
|
// new participant
|
||||||
|
this.members[from] = member;
|
||||||
|
this.list_members.push(from);
|
||||||
|
console.log('entered', from, member);
|
||||||
|
if (member.isFocus) {
|
||||||
|
focusMucJid = from;
|
||||||
|
console.info("Ignore focus: " + from + ", real JID: " + member.jid);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var id = $(pres).find('>userID').text();
|
||||||
|
var email = $(pres).find('>email');
|
||||||
|
if (email.length > 0) {
|
||||||
|
id = email.text();
|
||||||
|
}
|
||||||
|
UI.onMucEntered(from, id, member.displayName);
|
||||||
|
API.triggerEvent("participantJoined", {jid: from});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Presence update for existing participant
|
||||||
|
// Watch role change:
|
||||||
|
if (this.members[from].role != member.role) {
|
||||||
|
this.members[from].role = member.role;
|
||||||
|
UI.onMucRoleChanged(member.role, member.displayName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always trigger presence to update bindings
|
||||||
|
$(document).trigger('presence.muc', [from, member, pres]);
|
||||||
|
this.parsePresence(from, member, pres);
|
||||||
|
|
||||||
|
// Trigger status message update
|
||||||
|
if (member.status) {
|
||||||
|
UI.onMucPresenceStatus(from, member);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onPresenceUnavailable: function (pres) {
|
||||||
|
var from = pres.getAttribute('from');
|
||||||
|
// Status code 110 indicates that this notification is "self-presence".
|
||||||
|
if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
|
||||||
|
delete this.members[from];
|
||||||
|
this.list_members.splice(this.list_members.indexOf(from), 1);
|
||||||
|
this.onParticipantLeft(from);
|
||||||
|
}
|
||||||
|
// If the status code is 110 this means we're leaving and we would like
|
||||||
|
// to remove everyone else from our view, so we trigger the event.
|
||||||
|
else if (this.list_members.length > 1) {
|
||||||
|
for (var i = 0; i < this.list_members.length; i++) {
|
||||||
|
var member = this.list_members[i];
|
||||||
|
delete this.members[i];
|
||||||
|
this.list_members.splice(i, 1);
|
||||||
|
this.onParticipantLeft(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
|
||||||
|
$(document).trigger('kicked.muc', [from]);
|
||||||
|
if (this.myroomjid === from) {
|
||||||
|
XMPP.disposeConference(false);
|
||||||
|
eventEmitter.emit(XMPPEvents.KICKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
onPresenceError: function (pres) {
|
||||||
|
var from = pres.getAttribute('from');
|
||||||
|
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||||
|
console.log('on password required', from);
|
||||||
|
var self = this;
|
||||||
|
UI.onPasswordReqiured(function (value) {
|
||||||
|
self.doJoin(from, value);
|
||||||
|
});
|
||||||
|
} else if ($(pres).find(
|
||||||
|
'>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||||
|
var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
|
||||||
|
if (toDomain === config.hosts.anonymousdomain) {
|
||||||
|
// we are connected with anonymous domain and only non anonymous users can create rooms
|
||||||
|
// we must authorize the user
|
||||||
|
XMPP.promptLogin();
|
||||||
|
} else {
|
||||||
|
console.warn('onPresError ', pres);
|
||||||
|
UI.messageHandler.openReportDialog(null,
|
||||||
|
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
||||||
|
pres);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('onPresError ', pres);
|
||||||
|
UI.messageHandler.openReportDialog(null,
|
||||||
|
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
||||||
|
pres);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
sendMessage: function (body, nickname) {
|
||||||
|
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
||||||
|
msg.c('body', body).up();
|
||||||
|
if (nickname) {
|
||||||
|
msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
|
||||||
|
}
|
||||||
|
this.connection.send(msg);
|
||||||
|
API.triggerEvent("outgoingMessage", {"message": body});
|
||||||
|
},
|
||||||
|
setSubject: function (subject) {
|
||||||
|
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
||||||
|
msg.c('subject', subject);
|
||||||
|
this.connection.send(msg);
|
||||||
|
console.log("topic changed to " + subject);
|
||||||
|
},
|
||||||
|
onMessage: function (msg) {
|
||||||
|
// FIXME: this is a hack. but jingle on muc makes nickchanges hard
|
||||||
|
var from = msg.getAttribute('from');
|
||||||
|
var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);
|
||||||
|
|
||||||
|
var txt = $(msg).find('>body').text();
|
||||||
|
var type = msg.getAttribute("type");
|
||||||
|
if (type == "error") {
|
||||||
|
UI.chatAddError($(msg).find('>text').text(), txt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var subject = $(msg).find('>subject');
|
||||||
|
if (subject.length) {
|
||||||
|
var subjectText = subject.text();
|
||||||
|
if (subjectText || subjectText == "") {
|
||||||
|
UI.chatSetSubject(subjectText);
|
||||||
|
console.log("Subject is changed to " + subjectText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (txt) {
|
||||||
|
console.log('chat', nick, txt);
|
||||||
|
UI.updateChatConversation(from, nick, txt);
|
||||||
|
if (from != this.myroomjid)
|
||||||
|
API.triggerEvent("incomingMessage",
|
||||||
|
{"from": from, "nick": nick, "message": txt});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
lockRoom: function (key, onSuccess, onError, onNotSupported) {
|
||||||
|
//http://xmpp.org/extensions/xep-0045.html#roomconfig
|
||||||
|
var ob = this;
|
||||||
|
this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
|
||||||
|
function (res) {
|
||||||
|
if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
|
||||||
|
var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
||||||
|
formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||||
|
formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
||||||
|
formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
|
||||||
|
// Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
|
||||||
|
formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
|
||||||
|
// FIXME: is muc#roomconfig_passwordprotectedroom required?
|
||||||
|
this.connection.sendIQ(formsubmit,
|
||||||
|
onSuccess,
|
||||||
|
onError);
|
||||||
|
} else {
|
||||||
|
onNotSupported();
|
||||||
|
}
|
||||||
|
}, onError);
|
||||||
|
},
|
||||||
|
kick: function (jid) {
|
||||||
|
var kickIQ = $iq({to: this.roomjid, type: 'set'})
|
||||||
|
.c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
|
||||||
|
.c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
|
||||||
|
.c('reason').t('You have been kicked.').up().up().up();
|
||||||
|
|
||||||
|
this.connection.sendIQ(
|
||||||
|
kickIQ,
|
||||||
|
function (result) {
|
||||||
|
console.log('Kick participant with jid: ', jid, result);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('Kick participant error: ', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendPresence: function () {
|
||||||
|
var pres = $pres({to: this.presMap['to'] });
|
||||||
|
pres.c('x', {xmlns: this.presMap['xns']});
|
||||||
|
|
||||||
|
if (this.presMap['password']) {
|
||||||
|
pres.c('password').t(this.presMap['password']).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
pres.up();
|
||||||
|
|
||||||
|
// Send XEP-0115 'c' stanza that contains our capabilities info
|
||||||
|
if (this.connection.caps) {
|
||||||
|
this.connection.caps.node = config.clientNode;
|
||||||
|
pres.c('c', this.connection.caps.generateCapsAttrs()).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
|
||||||
|
.t(navigator.userAgent).up();
|
||||||
|
|
||||||
|
if (this.presMap['bridgeIsDown']) {
|
||||||
|
pres.c('bridgeIsDown').up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['email']) {
|
||||||
|
pres.c('email').t(this.presMap['email']).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['userId']) {
|
||||||
|
pres.c('userId').t(this.presMap['userId']).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['displayName']) {
|
||||||
|
// XEP-0172
|
||||||
|
pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
|
||||||
|
.t(this.presMap['displayName']).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['audions']) {
|
||||||
|
pres.c('audiomuted', {xmlns: this.presMap['audions']})
|
||||||
|
.t(this.presMap['audiomuted']).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['videons']) {
|
||||||
|
pres.c('videomuted', {xmlns: this.presMap['videons']})
|
||||||
|
.t(this.presMap['videomuted']).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['statsns']) {
|
||||||
|
var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
|
||||||
|
for (var stat in this.presMap["stats"])
|
||||||
|
if (this.presMap["stats"][stat] != null)
|
||||||
|
stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
|
||||||
|
pres.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['prezins']) {
|
||||||
|
pres.c('prezi',
|
||||||
|
{xmlns: this.presMap['prezins'],
|
||||||
|
'url': this.presMap['preziurl']})
|
||||||
|
.c('current').t(this.presMap['prezicurrent']).up().up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['etherpadns']) {
|
||||||
|
pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
|
||||||
|
.t(this.presMap['etherpadname']).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.presMap['medians']) {
|
||||||
|
pres.c('media', {xmlns: this.presMap['medians']});
|
||||||
|
var sourceNumber = 0;
|
||||||
|
Object.keys(this.presMap).forEach(function (key) {
|
||||||
|
if (key.indexOf('source') >= 0) {
|
||||||
|
sourceNumber++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (sourceNumber > 0)
|
||||||
|
for (var i = 1; i <= sourceNumber / 3; i++) {
|
||||||
|
pres.c('source',
|
||||||
|
{type: this.presMap['source' + i + '_type'],
|
||||||
|
ssrc: this.presMap['source' + i + '_ssrc'],
|
||||||
|
direction: this.presMap['source' + i + '_direction']
|
||||||
|
|| 'sendrecv' }
|
||||||
|
).up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pres.up();
|
||||||
|
// console.debug(pres.toString());
|
||||||
|
this.connection.send(pres);
|
||||||
|
},
|
||||||
|
addDisplayNameToPresence: function (displayName) {
|
||||||
|
this.presMap['displayName'] = displayName;
|
||||||
|
},
|
||||||
|
addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
|
||||||
|
if (!this.presMap['medians'])
|
||||||
|
this.presMap['medians'] = 'http://estos.de/ns/mjs';
|
||||||
|
|
||||||
|
this.presMap['source' + sourceNumber + '_type'] = mtype;
|
||||||
|
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
|
||||||
|
this.presMap['source' + sourceNumber + '_direction'] = direction;
|
||||||
|
},
|
||||||
|
clearPresenceMedia: function () {
|
||||||
|
var self = this;
|
||||||
|
Object.keys(this.presMap).forEach(function (key) {
|
||||||
|
if (key.indexOf('source') != -1) {
|
||||||
|
delete self.presMap[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
addPreziToPresence: function (url, currentSlide) {
|
||||||
|
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
|
||||||
|
this.presMap['preziurl'] = url;
|
||||||
|
this.presMap['prezicurrent'] = currentSlide;
|
||||||
|
},
|
||||||
|
removePreziFromPresence: function () {
|
||||||
|
delete this.presMap['prezins'];
|
||||||
|
delete this.presMap['preziurl'];
|
||||||
|
delete this.presMap['prezicurrent'];
|
||||||
|
},
|
||||||
|
addCurrentSlideToPresence: function (currentSlide) {
|
||||||
|
this.presMap['prezicurrent'] = currentSlide;
|
||||||
|
},
|
||||||
|
getPrezi: function (roomjid) {
|
||||||
|
return this.preziMap[roomjid];
|
||||||
|
},
|
||||||
|
addEtherpadToPresence: function (etherpadName) {
|
||||||
|
this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
|
||||||
|
this.presMap['etherpadname'] = etherpadName;
|
||||||
|
},
|
||||||
|
addAudioInfoToPresence: function (isMuted) {
|
||||||
|
this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
|
||||||
|
this.presMap['audiomuted'] = isMuted.toString();
|
||||||
|
},
|
||||||
|
addVideoInfoToPresence: function (isMuted) {
|
||||||
|
this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
|
||||||
|
this.presMap['videomuted'] = isMuted.toString();
|
||||||
|
},
|
||||||
|
addConnectionInfoToPresence: function (stats) {
|
||||||
|
this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
|
||||||
|
this.presMap['stats'] = stats;
|
||||||
|
},
|
||||||
|
findJidFromResource: function (resourceJid) {
|
||||||
|
if (resourceJid &&
|
||||||
|
resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
|
||||||
|
return this.myroomjid;
|
||||||
|
}
|
||||||
|
var peerJid = null;
|
||||||
|
Object.keys(this.members).some(function (jid) {
|
||||||
|
peerJid = jid;
|
||||||
|
return Strophe.getResourceFromJid(jid) === resourceJid;
|
||||||
|
});
|
||||||
|
return peerJid;
|
||||||
|
},
|
||||||
|
addBridgeIsDownToPresence: function () {
|
||||||
|
this.presMap['bridgeIsDown'] = true;
|
||||||
|
},
|
||||||
|
addEmailToPresence: function (email) {
|
||||||
|
this.presMap['email'] = email;
|
||||||
|
},
|
||||||
|
addUserIdToPresence: function (userId) {
|
||||||
|
this.presMap['userId'] = userId;
|
||||||
|
},
|
||||||
|
isModerator: function () {
|
||||||
|
return this.role === 'moderator';
|
||||||
|
},
|
||||||
|
getMemberRole: function (peerJid) {
|
||||||
|
if (this.members[peerJid]) {
|
||||||
|
return this.members[peerJid].role;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
onParticipantLeft: function (jid) {
|
||||||
|
UI.onMucLeft(jid);
|
||||||
|
|
||||||
|
API.triggerEvent("participantLeft", {jid: jid});
|
||||||
|
|
||||||
|
delete jid2Ssrc[jid];
|
||||||
|
|
||||||
|
this.connection.jingle.terminateByJid(jid);
|
||||||
|
|
||||||
|
if (this.getPrezi(jid)) {
|
||||||
|
$(document).trigger('presentationremoved.muc',
|
||||||
|
[jid, this.getPrezi(jid)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Moderator.onMucLeft(jid);
|
||||||
|
},
|
||||||
|
parsePresence: function (from, memeber, pres) {
|
||||||
|
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
|
||||||
|
bridgeIsDown = true;
|
||||||
|
eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(memeber.isFocus)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Remove old ssrcs coming from the jid
|
||||||
|
Object.keys(ssrc2jid).forEach(function (ssrc) {
|
||||||
|
if (ssrc2jid[ssrc] == jid) {
|
||||||
|
delete ssrc2jid[ssrc];
|
||||||
|
delete ssrc2videoType[ssrc];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var changedStreams = [];
|
||||||
|
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
|
||||||
|
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
|
||||||
|
var ssrcV = ssrc.getAttribute('ssrc');
|
||||||
|
ssrc2jid[ssrcV] = from;
|
||||||
|
notReceivedSSRCs.push(ssrcV);
|
||||||
|
|
||||||
|
var type = ssrc.getAttribute('type');
|
||||||
|
ssrc2videoType[ssrcV] = type;
|
||||||
|
|
||||||
|
var direction = ssrc.getAttribute('direction');
|
||||||
|
|
||||||
|
changedStreams.push({type: type, direction: direction});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
eventEmitter.emit(XMPPEvents.CHANGED_STREAMS, from, changedStreams);
|
||||||
|
|
||||||
|
var displayName = !config.displayJids
|
||||||
|
? memeber.displayName : Strophe.getResourceFromJid(from);
|
||||||
|
|
||||||
|
if (displayName && displayName.length > 0)
|
||||||
|
{
|
||||||
|
// $(document).trigger('displaynamechanged',
|
||||||
|
// [jid, displayName]);
|
||||||
|
eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var id = $(pres).find('>userID').text();
|
||||||
|
var email = $(pres).find('>email');
|
||||||
|
if(email.length > 0) {
|
||||||
|
id = email.text();
|
||||||
|
}
|
||||||
|
|
||||||
|
eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,334 @@
|
||||||
|
/* jshint -W117 */
|
||||||
|
|
||||||
|
var JingleSession = require("./JingleSession");
|
||||||
|
|
||||||
|
function CallIncomingJingle(sid, connection) {
|
||||||
|
var sess = connection.jingle.sessions[sid];
|
||||||
|
|
||||||
|
// TODO: do we check activecall == null?
|
||||||
|
activecall = sess;
|
||||||
|
|
||||||
|
statistics.onConferenceCreated(sess);
|
||||||
|
RTC.onConferenceCreated(sess);
|
||||||
|
|
||||||
|
// TODO: check affiliation and/or role
|
||||||
|
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
|
||||||
|
sess.usedrip = true; // not-so-naive trickle ice
|
||||||
|
sess.sendAnswer();
|
||||||
|
sess.accept();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = function(XMPP)
|
||||||
|
{
|
||||||
|
Strophe.addConnectionPlugin('jingle', {
|
||||||
|
connection: null,
|
||||||
|
sessions: {},
|
||||||
|
jid2session: {},
|
||||||
|
ice_config: {iceServers: []},
|
||||||
|
pc_constraints: {},
|
||||||
|
media_constraints: {
|
||||||
|
mandatory: {
|
||||||
|
'OfferToReceiveAudio': true,
|
||||||
|
'OfferToReceiveVideo': true
|
||||||
|
}
|
||||||
|
// MozDontOfferDataChannel: true when this is firefox
|
||||||
|
},
|
||||||
|
init: function (conn) {
|
||||||
|
this.connection = conn;
|
||||||
|
if (this.connection.disco) {
|
||||||
|
// http://xmpp.org/extensions/xep-0167.html#support
|
||||||
|
// http://xmpp.org/extensions/xep-0176.html#support
|
||||||
|
this.connection.disco.addFeature('urn:xmpp:jingle:1');
|
||||||
|
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
|
||||||
|
this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
|
||||||
|
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
|
||||||
|
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
|
||||||
|
|
||||||
|
|
||||||
|
// this is dealt with by SDP O/A so we don't need to annouce this
|
||||||
|
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
|
||||||
|
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
|
||||||
|
if (config.useRtcpMux) {
|
||||||
|
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
|
||||||
|
}
|
||||||
|
if (config.useBundle) {
|
||||||
|
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
|
||||||
|
}
|
||||||
|
//this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
|
||||||
|
}
|
||||||
|
this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
|
||||||
|
},
|
||||||
|
onJingle: function (iq) {
|
||||||
|
var sid = $(iq).find('jingle').attr('sid');
|
||||||
|
var action = $(iq).find('jingle').attr('action');
|
||||||
|
var fromJid = iq.getAttribute('from');
|
||||||
|
// send ack first
|
||||||
|
var ack = $iq({type: 'result',
|
||||||
|
to: fromJid,
|
||||||
|
id: iq.getAttribute('id')
|
||||||
|
});
|
||||||
|
console.log('on jingle ' + action + ' from ' + fromJid, iq);
|
||||||
|
var sess = this.sessions[sid];
|
||||||
|
if ('session-initiate' != action) {
|
||||||
|
if (sess === null) {
|
||||||
|
ack.type = 'error';
|
||||||
|
ack.c('error', {type: 'cancel'})
|
||||||
|
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||||
|
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||||
|
this.connection.send(ack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
|
||||||
|
// local jid is not checked
|
||||||
|
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
|
||||||
|
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
|
||||||
|
ack.type = 'error';
|
||||||
|
ack.c('error', {type: 'cancel'})
|
||||||
|
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||||
|
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||||
|
this.connection.send(ack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (sess !== undefined) {
|
||||||
|
// existing session with same session id
|
||||||
|
// this might be out-of-order if the sess.peerjid is the same as from
|
||||||
|
ack.type = 'error';
|
||||||
|
ack.c('error', {type: 'cancel'})
|
||||||
|
.c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
|
||||||
|
console.warn('duplicate session id', sid);
|
||||||
|
this.connection.send(ack);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// FIXME: check for a defined action
|
||||||
|
this.connection.send(ack);
|
||||||
|
// see http://xmpp.org/extensions/xep-0166.html#concepts-session
|
||||||
|
switch (action) {
|
||||||
|
case 'session-initiate':
|
||||||
|
sess = new JingleSession(
|
||||||
|
$(iq).attr('to'), $(iq).find('jingle').attr('sid'),
|
||||||
|
this.connection, XMPP);
|
||||||
|
// configure session
|
||||||
|
|
||||||
|
sess.media_constraints = this.media_constraints;
|
||||||
|
sess.pc_constraints = this.pc_constraints;
|
||||||
|
sess.ice_config = this.ice_config;
|
||||||
|
|
||||||
|
sess.initiate(fromJid, false);
|
||||||
|
// FIXME: setRemoteDescription should only be done when this call is to be accepted
|
||||||
|
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
|
||||||
|
|
||||||
|
this.sessions[sess.sid] = sess;
|
||||||
|
this.jid2session[sess.peerjid] = sess;
|
||||||
|
|
||||||
|
// the callback should either
|
||||||
|
// .sendAnswer and .accept
|
||||||
|
// or .sendTerminate -- not necessarily synchronus
|
||||||
|
CallIncomingJingle(sess.sid, this.connection);
|
||||||
|
break;
|
||||||
|
case 'session-accept':
|
||||||
|
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
|
||||||
|
sess.accept();
|
||||||
|
$(document).trigger('callaccepted.jingle', [sess.sid]);
|
||||||
|
break;
|
||||||
|
case 'session-terminate':
|
||||||
|
// If this is not the focus sending the terminate, we have
|
||||||
|
// nothing more to do here.
|
||||||
|
if (Object.keys(this.sessions).length < 1
|
||||||
|
|| !(this.sessions[Object.keys(this.sessions)[0]]
|
||||||
|
instanceof JingleSession))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.log('terminating...', sess.sid);
|
||||||
|
sess.terminate();
|
||||||
|
this.terminate(sess.sid);
|
||||||
|
if ($(iq).find('>jingle>reason').length) {
|
||||||
|
$(document).trigger('callterminated.jingle', [
|
||||||
|
sess.sid,
|
||||||
|
sess.peerjid,
|
||||||
|
$(iq).find('>jingle>reason>:first')[0].tagName,
|
||||||
|
$(iq).find('>jingle>reason>text').text()
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
$(document).trigger('callterminated.jingle',
|
||||||
|
[sess.sid, sess.peerjid]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'transport-info':
|
||||||
|
sess.addIceCandidate($(iq).find('>jingle>content'));
|
||||||
|
break;
|
||||||
|
case 'session-info':
|
||||||
|
var affected;
|
||||||
|
if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||||
|
$(document).trigger('ringing.jingle', [sess.sid]);
|
||||||
|
} else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||||
|
affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||||
|
$(document).trigger('mute.jingle', [sess.sid, affected]);
|
||||||
|
} else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||||
|
affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||||
|
$(document).trigger('unmute.jingle', [sess.sid, affected]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'addsource': // FIXME: proprietary, un-jingleish
|
||||||
|
case 'source-add': // FIXME: proprietary
|
||||||
|
sess.addSource($(iq).find('>jingle>content'), fromJid);
|
||||||
|
break;
|
||||||
|
case 'removesource': // FIXME: proprietary, un-jingleish
|
||||||
|
case 'source-remove': // FIXME: proprietary
|
||||||
|
sess.removeSource($(iq).find('>jingle>content'), fromJid);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn('jingle action not implemented', action);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
|
||||||
|
var sess = new JingleSession(myjid || this.connection.jid,
|
||||||
|
Math.random().toString(36).substr(2, 12), // random string
|
||||||
|
this.connection, XMPP);
|
||||||
|
// configure session
|
||||||
|
|
||||||
|
sess.media_constraints = this.media_constraints;
|
||||||
|
sess.pc_constraints = this.pc_constraints;
|
||||||
|
sess.ice_config = this.ice_config;
|
||||||
|
|
||||||
|
sess.initiate(peerjid, true);
|
||||||
|
this.sessions[sess.sid] = sess;
|
||||||
|
this.jid2session[sess.peerjid] = sess;
|
||||||
|
sess.sendOffer();
|
||||||
|
return sess;
|
||||||
|
},
|
||||||
|
terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
|
||||||
|
if (sid === null || sid === undefined) {
|
||||||
|
for (sid in this.sessions) {
|
||||||
|
if (this.sessions[sid].state != 'ended') {
|
||||||
|
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||||
|
this.sessions[sid].terminate();
|
||||||
|
}
|
||||||
|
delete this.jid2session[this.sessions[sid].peerjid];
|
||||||
|
delete this.sessions[sid];
|
||||||
|
}
|
||||||
|
} else if (this.sessions.hasOwnProperty(sid)) {
|
||||||
|
if (this.sessions[sid].state != 'ended') {
|
||||||
|
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||||
|
this.sessions[sid].terminate();
|
||||||
|
}
|
||||||
|
delete this.jid2session[this.sessions[sid].peerjid];
|
||||||
|
delete this.sessions[sid];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Used to terminate a session when an unavailable presence is received.
|
||||||
|
terminateByJid: function (jid) {
|
||||||
|
if (this.jid2session.hasOwnProperty(jid)) {
|
||||||
|
var sess = this.jid2session[jid];
|
||||||
|
if (sess) {
|
||||||
|
sess.terminate();
|
||||||
|
console.log('peer went away silently', jid);
|
||||||
|
delete this.sessions[sess.sid];
|
||||||
|
delete this.jid2session[jid];
|
||||||
|
$(document).trigger('callterminated.jingle',
|
||||||
|
[sess.sid, jid], 'gone');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
terminateRemoteByJid: function (jid, reason) {
|
||||||
|
if (this.jid2session.hasOwnProperty(jid)) {
|
||||||
|
var sess = this.jid2session[jid];
|
||||||
|
if (sess) {
|
||||||
|
sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
|
||||||
|
sess.terminate();
|
||||||
|
console.log('terminate peer with jid', sess.sid, jid);
|
||||||
|
delete this.sessions[sess.sid];
|
||||||
|
delete this.jid2session[jid];
|
||||||
|
$(document).trigger('callterminated.jingle',
|
||||||
|
[sess.sid, jid, 'kicked']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getStunAndTurnCredentials: function () {
|
||||||
|
// get stun and turn configuration from server via xep-0215
|
||||||
|
// uses time-limited credentials as described in
|
||||||
|
// http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
|
||||||
|
//
|
||||||
|
// see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
|
||||||
|
// for a prosody module which implements this
|
||||||
|
//
|
||||||
|
// currently, this doesn't work with updateIce and therefore credentials with a long
|
||||||
|
// validity have to be fetched before creating the peerconnection
|
||||||
|
// TODO: implement refresh via updateIce as described in
|
||||||
|
// https://code.google.com/p/webrtc/issues/detail?id=1650
|
||||||
|
var self = this;
|
||||||
|
this.connection.sendIQ(
|
||||||
|
$iq({type: 'get', to: this.connection.domain})
|
||||||
|
.c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
|
||||||
|
function (res) {
|
||||||
|
var iceservers = [];
|
||||||
|
$(res).find('>services>service').each(function (idx, el) {
|
||||||
|
el = $(el);
|
||||||
|
var dict = {};
|
||||||
|
var type = el.attr('type');
|
||||||
|
switch (type) {
|
||||||
|
case 'stun':
|
||||||
|
dict.url = 'stun:' + el.attr('host');
|
||||||
|
if (el.attr('port')) {
|
||||||
|
dict.url += ':' + el.attr('port');
|
||||||
|
}
|
||||||
|
iceservers.push(dict);
|
||||||
|
break;
|
||||||
|
case 'turn':
|
||||||
|
case 'turns':
|
||||||
|
dict.url = type + ':';
|
||||||
|
if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
|
||||||
|
if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
|
||||||
|
dict.url += el.attr('username') + '@';
|
||||||
|
} else {
|
||||||
|
dict.username = el.attr('username'); // only works in M28
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dict.url += el.attr('host');
|
||||||
|
if (el.attr('port') && el.attr('port') != '3478') {
|
||||||
|
dict.url += ':' + el.attr('port');
|
||||||
|
}
|
||||||
|
if (el.attr('transport') && el.attr('transport') != 'udp') {
|
||||||
|
dict.url += '?transport=' + el.attr('transport');
|
||||||
|
}
|
||||||
|
if (el.attr('password')) {
|
||||||
|
dict.credential = el.attr('password');
|
||||||
|
}
|
||||||
|
iceservers.push(dict);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.ice_config.iceServers = iceservers;
|
||||||
|
},
|
||||||
|
function (err) {
|
||||||
|
console.warn('getting turn credentials failed', err);
|
||||||
|
console.warn('is mod_turncredentials or similar installed?');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// implement push?
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates the log data
|
||||||
|
*/
|
||||||
|
populateData: function () {
|
||||||
|
var data = {};
|
||||||
|
Object.keys(this.sessions).forEach(function (sid) {
|
||||||
|
var session = this.sessions[sid];
|
||||||
|
if (session.peerconnection && session.peerconnection.updateLog) {
|
||||||
|
// FIXME: should probably be a .dump call
|
||||||
|
data["jingle_" + session.sid] = {
|
||||||
|
updateLog: session.peerconnection.updateLog,
|
||||||
|
stats: session.peerconnection.stats,
|
||||||
|
url: window.location.href
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
/* global Strophe */
|
||||||
|
module.exports = function () {
|
||||||
|
|
||||||
|
Strophe.addConnectionPlugin('logger', {
|
||||||
|
// logs raw stanzas and makes them available for download as JSON
|
||||||
|
connection: null,
|
||||||
|
log: [],
|
||||||
|
init: function (conn) {
|
||||||
|
this.connection = conn;
|
||||||
|
this.connection.rawInput = this.log_incoming.bind(this);
|
||||||
|
this.connection.rawOutput = this.log_outgoing.bind(this);
|
||||||
|
},
|
||||||
|
log_incoming: function (stanza) {
|
||||||
|
this.log.push([new Date().getTime(), 'incoming', stanza]);
|
||||||
|
},
|
||||||
|
log_outgoing: function (stanza) {
|
||||||
|
this.log.push([new Date().getTime(), 'outgoing', stanza]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,58 @@
|
||||||
|
/* global $, $iq, config, connection, focusMucJid, forceMuted,
|
||||||
|
setAudioMuted, Strophe */
|
||||||
|
/**
|
||||||
|
* Moderate connection plugin.
|
||||||
|
*/
|
||||||
|
module.exports = function (XMPP) {
|
||||||
|
Strophe.addConnectionPlugin('moderate', {
|
||||||
|
connection: null,
|
||||||
|
init: function (conn) {
|
||||||
|
this.connection = conn;
|
||||||
|
|
||||||
|
this.connection.addHandler(this.onMute.bind(this),
|
||||||
|
'http://jitsi.org/jitmeet/audio',
|
||||||
|
'iq',
|
||||||
|
'set',
|
||||||
|
null,
|
||||||
|
null);
|
||||||
|
},
|
||||||
|
setMute: function (jid, mute) {
|
||||||
|
console.info("set mute", mute);
|
||||||
|
var iqToFocus = $iq({to: focusMucJid, type: 'set'})
|
||||||
|
.c('mute', {
|
||||||
|
xmlns: 'http://jitsi.org/jitmeet/audio',
|
||||||
|
jid: jid
|
||||||
|
})
|
||||||
|
.t(mute.toString())
|
||||||
|
.up();
|
||||||
|
|
||||||
|
this.connection.sendIQ(
|
||||||
|
iqToFocus,
|
||||||
|
function (result) {
|
||||||
|
console.log('set mute', result);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('set mute error', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onMute: function (iq) {
|
||||||
|
var from = iq.getAttribute('from');
|
||||||
|
if (from !== focusMucJid) {
|
||||||
|
console.warn("Ignored mute from non focus peer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var mute = $(iq).find('mute');
|
||||||
|
if (mute.length) {
|
||||||
|
var doMuteAudio = mute.text() === "true";
|
||||||
|
UI.setAudioMuted(doMuteAudio);
|
||||||
|
XMPP.forceMuted = doMuteAudio;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
eject: function (jid) {
|
||||||
|
// We're not the focus, so can't terminate
|
||||||
|
//connection.jingle.terminateRemoteByJid(jid, 'kick');
|
||||||
|
this.connection.emuc.kick(jid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
/* jshint -W117 */
|
||||||
|
module.exports = function() {
|
||||||
|
Strophe.addConnectionPlugin('rayo',
|
||||||
|
{
|
||||||
|
RAYO_XMLNS: 'urn:xmpp:rayo:1',
|
||||||
|
connection: null,
|
||||||
|
init: function (conn) {
|
||||||
|
this.connection = conn;
|
||||||
|
if (this.connection.disco) {
|
||||||
|
this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.addHandler(
|
||||||
|
this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
|
||||||
|
},
|
||||||
|
onRayo: function (iq) {
|
||||||
|
console.info("Rayo IQ", iq);
|
||||||
|
},
|
||||||
|
dial: function (to, from, roomName, roomPass) {
|
||||||
|
var self = this;
|
||||||
|
var req = $iq(
|
||||||
|
{
|
||||||
|
type: 'set',
|
||||||
|
to: focusMucJid
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.c('dial',
|
||||||
|
{
|
||||||
|
xmlns: this.RAYO_XMLNS,
|
||||||
|
to: to,
|
||||||
|
from: from
|
||||||
|
});
|
||||||
|
req.c('header',
|
||||||
|
{
|
||||||
|
name: 'JvbRoomName',
|
||||||
|
value: roomName
|
||||||
|
}).up();
|
||||||
|
|
||||||
|
if (roomPass !== null && roomPass.length) {
|
||||||
|
|
||||||
|
req.c('header',
|
||||||
|
{
|
||||||
|
name: 'JvbRoomPassword',
|
||||||
|
value: roomPass
|
||||||
|
}).up();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.connection.sendIQ(
|
||||||
|
req,
|
||||||
|
function (result) {
|
||||||
|
console.info('Dial result ', result);
|
||||||
|
|
||||||
|
var resource = $(result).find('ref').attr('uri');
|
||||||
|
this.call_resource = resource.substr('xmpp:'.length);
|
||||||
|
console.info(
|
||||||
|
"Received call resource: " + this.call_resource);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.info('Dial error ', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
hang_up: function () {
|
||||||
|
if (!this.call_resource) {
|
||||||
|
console.warn("No call in progress");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var req = $iq(
|
||||||
|
{
|
||||||
|
type: 'set',
|
||||||
|
to: this.call_resource
|
||||||
|
}
|
||||||
|
);
|
||||||
|
req.c('hangup',
|
||||||
|
{
|
||||||
|
xmlns: this.RAYO_XMLNS
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connection.sendIQ(
|
||||||
|
req,
|
||||||
|
function (result) {
|
||||||
|
console.info('Hangup result ', result);
|
||||||
|
self.call_resource = null;
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.info('Hangup error ', error);
|
||||||
|
self.call_resource = null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,42 @@
|
||||||
|
/**
|
||||||
|
* Strophe logger implementation. Logs from level WARN and above.
|
||||||
|
*/
|
||||||
|
module.exports = function () {
|
||||||
|
|
||||||
|
Strophe.log = function (level, msg) {
|
||||||
|
switch (level) {
|
||||||
|
case Strophe.LogLevel.WARN:
|
||||||
|
console.warn("Strophe: " + msg);
|
||||||
|
break;
|
||||||
|
case Strophe.LogLevel.ERROR:
|
||||||
|
case Strophe.LogLevel.FATAL:
|
||||||
|
console.error("Strophe: " + msg);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Strophe.getStatusString = function (status) {
|
||||||
|
switch (status) {
|
||||||
|
case Strophe.Status.ERROR:
|
||||||
|
return "ERROR";
|
||||||
|
case Strophe.Status.CONNECTING:
|
||||||
|
return "CONNECTING";
|
||||||
|
case Strophe.Status.CONNFAIL:
|
||||||
|
return "CONNFAIL";
|
||||||
|
case Strophe.Status.AUTHENTICATING:
|
||||||
|
return "AUTHENTICATING";
|
||||||
|
case Strophe.Status.AUTHFAIL:
|
||||||
|
return "AUTHFAIL";
|
||||||
|
case Strophe.Status.CONNECTED:
|
||||||
|
return "CONNECTED";
|
||||||
|
case Strophe.Status.DISCONNECTED:
|
||||||
|
return "DISCONNECTED";
|
||||||
|
case Strophe.Status.DISCONNECTING:
|
||||||
|
return "DISCONNECTING";
|
||||||
|
case Strophe.Status.ATTACHED:
|
||||||
|
return "ATTACHED";
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,422 @@
|
||||||
|
var Moderator = require("./moderator");
|
||||||
|
var EventEmitter = require("events");
|
||||||
|
var Recording = require("./recording");
|
||||||
|
var SDP = require("./SDP");
|
||||||
|
|
||||||
|
var eventEmitter = new EventEmitter();
|
||||||
|
var connection = null;
|
||||||
|
var authenticatedUser = false;
|
||||||
|
var activecall = null;
|
||||||
|
|
||||||
|
function connect(jid, password, uiCredentials) {
|
||||||
|
var bosh
|
||||||
|
= uiCredentials.bosh || config.bosh || '/http-bind';
|
||||||
|
connection = new Strophe.Connection(bosh);
|
||||||
|
Moderator.setConnection(connection);
|
||||||
|
|
||||||
|
var settings = UI.getSettings();
|
||||||
|
var email = settings.email;
|
||||||
|
var displayName = settings.displayName;
|
||||||
|
if(email) {
|
||||||
|
connection.emuc.addEmailToPresence(email);
|
||||||
|
} else {
|
||||||
|
connection.emuc.addUserIdToPresence(settings.uid);
|
||||||
|
}
|
||||||
|
if(displayName) {
|
||||||
|
connection.emuc.addDisplayNameToPresence(displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection.disco) {
|
||||||
|
// for chrome, add multistream cap
|
||||||
|
}
|
||||||
|
connection.jingle.pc_constraints = RTC.getPCConstraints();
|
||||||
|
if (config.useIPv6) {
|
||||||
|
// https://code.google.com/p/webrtc/issues/detail?id=2828
|
||||||
|
if (!connection.jingle.pc_constraints.optional)
|
||||||
|
connection.jingle.pc_constraints.optional = [];
|
||||||
|
connection.jingle.pc_constraints.optional.push({googIPv6: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!password)
|
||||||
|
password = uiCredentials.password;
|
||||||
|
|
||||||
|
var anonymousConnectionFailed = false;
|
||||||
|
connection.connect(jid, password, function (status, msg) {
|
||||||
|
console.log('Strophe status changed to',
|
||||||
|
Strophe.getStatusString(status));
|
||||||
|
if (status === Strophe.Status.CONNECTED) {
|
||||||
|
if (config.useStunTurn) {
|
||||||
|
connection.jingle.getStunAndTurnCredentials();
|
||||||
|
}
|
||||||
|
UI.disableConnect();
|
||||||
|
|
||||||
|
console.info("My Jabber ID: " + connection.jid);
|
||||||
|
|
||||||
|
if(password)
|
||||||
|
authenticatedUser = true;
|
||||||
|
maybeDoJoin();
|
||||||
|
} else if (status === Strophe.Status.CONNFAIL) {
|
||||||
|
if(msg === 'x-strophe-bad-non-anon-jid') {
|
||||||
|
anonymousConnectionFailed = true;
|
||||||
|
}
|
||||||
|
} else if (status === Strophe.Status.DISCONNECTED) {
|
||||||
|
if(anonymousConnectionFailed) {
|
||||||
|
// prompt user for username and password
|
||||||
|
XMPP.promptLogin();
|
||||||
|
}
|
||||||
|
} else if (status === Strophe.Status.AUTHFAIL) {
|
||||||
|
// wrong password or username, prompt user
|
||||||
|
XMPP.promptLogin();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function maybeDoJoin() {
|
||||||
|
if (connection && connection.connected &&
|
||||||
|
Strophe.getResourceFromJid(connection.jid)
|
||||||
|
&& (RTC.localAudio || RTC.localVideo)) {
|
||||||
|
// .connected is true while connecting?
|
||||||
|
doJoin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function doJoin() {
|
||||||
|
var roomName = UI.generateRoomName();
|
||||||
|
|
||||||
|
Moderator.allocateConferenceFocus(
|
||||||
|
roomName, UI.checkForNicknameAndJoin);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initStrophePlugins()
|
||||||
|
{
|
||||||
|
require("./strophe.emuc")(XMPP, eventEmitter);
|
||||||
|
require("./strophe.jingle")();
|
||||||
|
require("./strophe.moderate")(XMPP);
|
||||||
|
require("./strophe.util")();
|
||||||
|
require("./strophe.rayo")();
|
||||||
|
require("./strophe.logger")();
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerListeners() {
|
||||||
|
RTC.addStreamListener(maybeDoJoin,
|
||||||
|
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupEvents() {
|
||||||
|
$(window).bind('beforeunload', function () {
|
||||||
|
if (connection && connection.connected) {
|
||||||
|
// ensure signout
|
||||||
|
$.ajax({
|
||||||
|
type: 'POST',
|
||||||
|
url: config.bosh,
|
||||||
|
async: false,
|
||||||
|
cache: false,
|
||||||
|
contentType: 'application/xml',
|
||||||
|
data: "<body rid='" + (connection.rid || connection._proto.rid)
|
||||||
|
+ "' xmlns='http://jabber.org/protocol/httpbind' sid='"
|
||||||
|
+ (connection.sid || connection._proto.sid)
|
||||||
|
+ "' type='terminate'>" +
|
||||||
|
"<presence xmlns='jabber:client' type='unavailable'/>" +
|
||||||
|
"</body>",
|
||||||
|
success: function (data) {
|
||||||
|
console.log('signed out');
|
||||||
|
console.log(data);
|
||||||
|
},
|
||||||
|
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
||||||
|
console.log('signout error',
|
||||||
|
textStatus + ' (' + errorThrown + ')');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
XMPP.disposeConference(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var XMPP = {
|
||||||
|
sessionTerminated: false,
|
||||||
|
/**
|
||||||
|
* Remembers if we were muted by the focus.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
forceMuted: false,
|
||||||
|
start: function (uiCredentials) {
|
||||||
|
setupEvents();
|
||||||
|
initStrophePlugins();
|
||||||
|
registerListeners();
|
||||||
|
Moderator.init();
|
||||||
|
var jid = uiCredentials.jid ||
|
||||||
|
config.hosts.anonymousdomain ||
|
||||||
|
config.hosts.domain ||
|
||||||
|
window.location.hostname;
|
||||||
|
connect(jid, null, uiCredentials);
|
||||||
|
},
|
||||||
|
promptLogin: function () {
|
||||||
|
UI.showLoginPopup(connect);
|
||||||
|
},
|
||||||
|
joinRooom: function(roomName, useNicks, nick)
|
||||||
|
{
|
||||||
|
var roomjid;
|
||||||
|
roomjid = roomName;
|
||||||
|
|
||||||
|
if (useNicks) {
|
||||||
|
if (nick) {
|
||||||
|
roomjid += '/' + nick;
|
||||||
|
} else {
|
||||||
|
roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
var tmpJid = Strophe.getNodeFromJid(connection.jid);
|
||||||
|
|
||||||
|
if(!authenticatedUser)
|
||||||
|
tmpJid = tmpJid.substr(0, 8);
|
||||||
|
|
||||||
|
roomjid += '/' + tmpJid;
|
||||||
|
}
|
||||||
|
connection.emuc.doJoin(roomjid);
|
||||||
|
},
|
||||||
|
myJid: function () {
|
||||||
|
if(!connection)
|
||||||
|
return null;
|
||||||
|
return connection.emuc.myroomjid;
|
||||||
|
},
|
||||||
|
myResource: function () {
|
||||||
|
if(!connection || ! connection.emuc.myroomjid)
|
||||||
|
return null;
|
||||||
|
return Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
||||||
|
},
|
||||||
|
disposeConference: function (onUnload) {
|
||||||
|
eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
|
||||||
|
var handler = activecall;
|
||||||
|
if (handler && handler.peerconnection) {
|
||||||
|
// FIXME: probably removing streams is not required and close() should
|
||||||
|
// be enough
|
||||||
|
if (RTC.localAudio) {
|
||||||
|
handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload);
|
||||||
|
}
|
||||||
|
if (RTC.localVideo) {
|
||||||
|
handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload);
|
||||||
|
}
|
||||||
|
handler.peerconnection.close();
|
||||||
|
}
|
||||||
|
activecall = null;
|
||||||
|
if(!onUnload)
|
||||||
|
{
|
||||||
|
this.sessionTerminated = true;
|
||||||
|
connection.emuc.doLeave();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addListener: function(type, listener)
|
||||||
|
{
|
||||||
|
eventEmitter.on(type, listener);
|
||||||
|
},
|
||||||
|
removeListener: function (type, listener) {
|
||||||
|
eventEmitter.removeListener(type, listener);
|
||||||
|
},
|
||||||
|
allocateConferenceFocus: function(roomName, callback) {
|
||||||
|
Moderator.allocateConferenceFocus(roomName, callback);
|
||||||
|
},
|
||||||
|
isModerator: function () {
|
||||||
|
return Moderator.isModerator();
|
||||||
|
},
|
||||||
|
isSipGatewayEnabled: function () {
|
||||||
|
return Moderator.isSipGatewayEnabled();
|
||||||
|
},
|
||||||
|
isExternalAuthEnabled: function () {
|
||||||
|
return Moderator.isExternalAuthEnabled();
|
||||||
|
},
|
||||||
|
switchStreams: function (stream, oldStream, callback) {
|
||||||
|
if (activecall) {
|
||||||
|
// FIXME: will block switchInProgress on true value in case of exception
|
||||||
|
activecall.switchStreams(stream, oldStream, callback);
|
||||||
|
} else {
|
||||||
|
// We are done immediately
|
||||||
|
console.error("No conference handler");
|
||||||
|
UI.messageHandler.showError('Error',
|
||||||
|
'Unable to switch video stream.');
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setVideoMute: function (mute, callback, options) {
|
||||||
|
if(activecall && connection && RTC.localVideo)
|
||||||
|
{
|
||||||
|
activecall.setVideoMute(mute, callback, options);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setAudioMute: function (mute, callback) {
|
||||||
|
if (!(connection && RTC.localAudio)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (this.forceMuted && !mute) {
|
||||||
|
console.info("Asking focus for unmute");
|
||||||
|
connection.moderate.setMute(connection.emuc.myroomjid, mute);
|
||||||
|
// FIXME: wait for result before resetting muted status
|
||||||
|
this.forceMuted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mute == RTC.localAudio.isMuted()) {
|
||||||
|
// Nothing to do
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is not clear what is the right way to handle multiple tracks.
|
||||||
|
// So at least make sure that they are all muted or all unmuted and
|
||||||
|
// that we send presence just once.
|
||||||
|
RTC.localAudio.mute();
|
||||||
|
// isMuted is the opposite of audioEnabled
|
||||||
|
connection.emuc.addAudioInfoToPresence(mute);
|
||||||
|
connection.emuc.sendPresence();
|
||||||
|
callback();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
// Really mute video, i.e. dont even send black frames
|
||||||
|
muteVideo: function (pc, unmute) {
|
||||||
|
// FIXME: this probably needs another of those lovely state safeguards...
|
||||||
|
// which checks for iceconn == connected and sigstate == stable
|
||||||
|
pc.setRemoteDescription(pc.remoteDescription,
|
||||||
|
function () {
|
||||||
|
pc.createAnswer(
|
||||||
|
function (answer) {
|
||||||
|
var sdp = new SDP(answer.sdp);
|
||||||
|
if (sdp.media.length > 1) {
|
||||||
|
if (unmute)
|
||||||
|
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
|
||||||
|
else
|
||||||
|
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
|
||||||
|
sdp.raw = sdp.session + sdp.media.join('');
|
||||||
|
answer.sdp = sdp.raw;
|
||||||
|
}
|
||||||
|
pc.setLocalDescription(answer,
|
||||||
|
function () {
|
||||||
|
console.log('mute SLD ok');
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('mute SLD error');
|
||||||
|
UI.messageHandler.showError('Error',
|
||||||
|
'Oops! Something went wrong and we failed to ' +
|
||||||
|
'mute! (SLD Failure)');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log(error);
|
||||||
|
UI.messageHandler.showError();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
console.log('muteVideo SRD error');
|
||||||
|
UI.messageHandler.showError('Error',
|
||||||
|
'Oops! Something went wrong and we failed to stop video!' +
|
||||||
|
'(SRD Failure)');
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
toggleRecording: function (tokenEmptyCallback,
|
||||||
|
startingCallback, startedCallback) {
|
||||||
|
Recording.toggleRecording(tokenEmptyCallback,
|
||||||
|
startingCallback, startedCallback);
|
||||||
|
},
|
||||||
|
addToPresence: function (name, value, dontSend) {
|
||||||
|
switch (name)
|
||||||
|
{
|
||||||
|
case "displayName":
|
||||||
|
connection.emuc.addDisplayNameToPresence(value);
|
||||||
|
break;
|
||||||
|
case "etherpad":
|
||||||
|
connection.emuc.addEtherpadToPresence(value);
|
||||||
|
break;
|
||||||
|
case "prezi":
|
||||||
|
connection.emuc.addPreziToPresence(value, 0);
|
||||||
|
break;
|
||||||
|
case "preziSlide":
|
||||||
|
connection.emuc.addCurrentSlideToPresence(value);
|
||||||
|
break;
|
||||||
|
case "connectionQuality":
|
||||||
|
connection.emuc.addConnectionInfoToPresence(value);
|
||||||
|
break;
|
||||||
|
case "email":
|
||||||
|
connection.emuc.addEmailToPresence(value);
|
||||||
|
default :
|
||||||
|
console.log("Unknown tag for presence.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!dontSend)
|
||||||
|
connection.emuc.sendPresence();
|
||||||
|
},
|
||||||
|
sendLogs: function (data) {
|
||||||
|
if(!focusMucJid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var deflate = true;
|
||||||
|
|
||||||
|
var content = JSON.stringify(dataYes);
|
||||||
|
if (deflate) {
|
||||||
|
content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
|
||||||
|
}
|
||||||
|
content = Base64.encode(content);
|
||||||
|
// XEP-0337-ish
|
||||||
|
var message = $msg({to: focusMucJid, type: 'normal'});
|
||||||
|
message.c('log', { xmlns: 'urn:xmpp:eventlog',
|
||||||
|
id: 'PeerConnectionStats'});
|
||||||
|
message.c('message').t(content).up();
|
||||||
|
if (deflate) {
|
||||||
|
message.c('tag', {name: "deflated", value: "true"}).up();
|
||||||
|
}
|
||||||
|
message.up();
|
||||||
|
|
||||||
|
connection.send(message);
|
||||||
|
},
|
||||||
|
populateData: function () {
|
||||||
|
var data = {};
|
||||||
|
if (connection.jingle) {
|
||||||
|
data = connection.jingle.populateData();
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
getLogger: function () {
|
||||||
|
if(connection.logger)
|
||||||
|
return connection.logger.log;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
getPrezi: function () {
|
||||||
|
return connection.emuc.getPrezi(this.myJid());
|
||||||
|
},
|
||||||
|
removePreziFromPresence: function () {
|
||||||
|
connection.emuc.removePreziFromPresence();
|
||||||
|
connection.emuc.sendPresence();
|
||||||
|
},
|
||||||
|
sendChatMessage: function (message, nickname) {
|
||||||
|
connection.emuc.sendMessage(message, nickname);
|
||||||
|
},
|
||||||
|
setSubject: function (topic) {
|
||||||
|
connection.emuc.setSubject(topic);
|
||||||
|
},
|
||||||
|
lockRoom: function (key, onSuccess, onError, onNotSupported) {
|
||||||
|
connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
|
||||||
|
},
|
||||||
|
dial: function (to, from, roomName,roomPass) {
|
||||||
|
connection.rayo.dial(to, from, roomName,roomPass);
|
||||||
|
},
|
||||||
|
setMute: function (jid, mute) {
|
||||||
|
connection.moderate.setMute(jid, mute);
|
||||||
|
},
|
||||||
|
eject: function (jid) {
|
||||||
|
connection.moderate.eject(jid);
|
||||||
|
},
|
||||||
|
findJidFromResource: function (resource) {
|
||||||
|
connection.emuc.findJidFromResource(resource);
|
||||||
|
},
|
||||||
|
getMembers: function () {
|
||||||
|
return connection.emuc.members;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = XMPP;
|
548
muc.js
548
muc.js
|
@ -1,548 +0,0 @@
|
||||||
/* jshint -W117 */
|
|
||||||
/* a simple MUC connection plugin
|
|
||||||
* can only handle a single MUC room
|
|
||||||
*/
|
|
||||||
Strophe.addConnectionPlugin('emuc', {
|
|
||||||
connection: null,
|
|
||||||
roomjid: null,
|
|
||||||
myroomjid: null,
|
|
||||||
members: {},
|
|
||||||
list_members: [], // so we can elect a new focus
|
|
||||||
presMap: {},
|
|
||||||
preziMap: {},
|
|
||||||
joined: false,
|
|
||||||
isOwner: false,
|
|
||||||
role: null,
|
|
||||||
init: function (conn) {
|
|
||||||
this.connection = conn;
|
|
||||||
},
|
|
||||||
initPresenceMap: function (myroomjid) {
|
|
||||||
this.presMap['to'] = myroomjid;
|
|
||||||
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
|
|
||||||
},
|
|
||||||
doJoin: function (jid, password) {
|
|
||||||
this.myroomjid = jid;
|
|
||||||
|
|
||||||
console.info("Joined MUC as " + this.myroomjid);
|
|
||||||
|
|
||||||
this.initPresenceMap(this.myroomjid);
|
|
||||||
|
|
||||||
if (!this.roomjid) {
|
|
||||||
this.roomjid = Strophe.getBareJidFromJid(jid);
|
|
||||||
// add handlers (just once)
|
|
||||||
this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
|
|
||||||
this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
|
|
||||||
this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
|
|
||||||
this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
|
|
||||||
}
|
|
||||||
if (password !== undefined) {
|
|
||||||
this.presMap['password'] = password;
|
|
||||||
}
|
|
||||||
this.sendPresence();
|
|
||||||
},
|
|
||||||
doLeave: function() {
|
|
||||||
console.log("do leave", this.myroomjid);
|
|
||||||
var pres = $pres({to: this.myroomjid, type: 'unavailable' });
|
|
||||||
this.presMap.length = 0;
|
|
||||||
this.connection.send(pres);
|
|
||||||
},
|
|
||||||
createNonAnonymousRoom: function() {
|
|
||||||
// http://xmpp.org/extensions/xep-0045.html#createroom-reserved
|
|
||||||
|
|
||||||
var getForm = $iq({type: 'get', to: this.roomjid})
|
|
||||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
|
|
||||||
.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
|
||||||
|
|
||||||
this.connection.sendIQ(getForm, function (form){
|
|
||||||
|
|
||||||
if (!$(form).find(
|
|
||||||
'>query>x[xmlns="jabber:x:data"]' +
|
|
||||||
'>field[var="muc#roomconfig_whois"]').length) {
|
|
||||||
|
|
||||||
console.error('non-anonymous rooms not supported');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var formSubmit = $iq({to: this.roomjid, type: 'set'})
|
|
||||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
|
||||||
|
|
||||||
formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
|
||||||
|
|
||||||
formSubmit.c('field', {'var': 'FORM_TYPE'})
|
|
||||||
.c('value')
|
|
||||||
.t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
|
||||||
|
|
||||||
formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
|
|
||||||
.c('value').t('anyone').up().up();
|
|
||||||
|
|
||||||
this.connection.sendIQ(formSubmit);
|
|
||||||
|
|
||||||
}, function (error){
|
|
||||||
console.error("Error getting room configuration form");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onPresence: function (pres) {
|
|
||||||
var from = pres.getAttribute('from');
|
|
||||||
|
|
||||||
// What is this for? A workaround for something?
|
|
||||||
if (pres.getAttribute('type')) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse etherpad tag.
|
|
||||||
var etherpad = $(pres).find('>etherpad');
|
|
||||||
if (etherpad.length) {
|
|
||||||
if (config.etherpad_base && !Moderator.isModerator()) {
|
|
||||||
UI.initEtherpad(etherpad.text());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse prezi tag.
|
|
||||||
var presentation = $(pres).find('>prezi');
|
|
||||||
if (presentation.length)
|
|
||||||
{
|
|
||||||
var url = presentation.attr('url');
|
|
||||||
var current = presentation.find('>current').text();
|
|
||||||
|
|
||||||
console.log('presentation info received from', from, url);
|
|
||||||
|
|
||||||
if (this.preziMap[from] == null) {
|
|
||||||
this.preziMap[from] = url;
|
|
||||||
|
|
||||||
$(document).trigger('presentationadded.muc', [from, url, current]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$(document).trigger('gotoslide.muc', [from, url, current]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (this.preziMap[from] != null) {
|
|
||||||
var url = this.preziMap[from];
|
|
||||||
delete this.preziMap[from];
|
|
||||||
$(document).trigger('presentationremoved.muc', [from, url]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse audio info tag.
|
|
||||||
var audioMuted = $(pres).find('>audiomuted');
|
|
||||||
if (audioMuted.length) {
|
|
||||||
$(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse video info tag.
|
|
||||||
var videoMuted = $(pres).find('>videomuted');
|
|
||||||
if (videoMuted.length) {
|
|
||||||
$(document).trigger('videomuted.muc', [from, videoMuted.text()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
var stats = $(pres).find('>stats');
|
|
||||||
if(stats.length)
|
|
||||||
{
|
|
||||||
var statsObj = {};
|
|
||||||
Strophe.forEachChild(stats[0], "stat", function (el) {
|
|
||||||
statsObj[el.getAttribute("name")] = el.getAttribute("value");
|
|
||||||
});
|
|
||||||
connectionquality.updateRemoteStats(from, statsObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse status.
|
|
||||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
|
|
||||||
this.isOwner = true;
|
|
||||||
this.createNonAnonymousRoom();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse roles.
|
|
||||||
var member = {};
|
|
||||||
member.show = $(pres).find('>show').text();
|
|
||||||
member.status = $(pres).find('>status').text();
|
|
||||||
var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
|
|
||||||
member.affiliation = tmp.attr('affiliation');
|
|
||||||
member.role = tmp.attr('role');
|
|
||||||
|
|
||||||
// Focus recognition
|
|
||||||
member.jid = tmp.attr('jid');
|
|
||||||
member.isFocus = false;
|
|
||||||
if (member.jid
|
|
||||||
&& member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
|
|
||||||
member.isFocus = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
|
|
||||||
member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
|
|
||||||
|
|
||||||
if (from == this.myroomjid) {
|
|
||||||
if (member.affiliation == 'owner') this.isOwner = true;
|
|
||||||
if (this.role !== member.role) {
|
|
||||||
this.role = member.role;
|
|
||||||
if(Moderator.onLocalRoleChange)
|
|
||||||
Moderator.onLocalRoleChange(from, member, pres);
|
|
||||||
UI.onLocalRoleChange(from, member, pres);
|
|
||||||
}
|
|
||||||
if (!this.joined) {
|
|
||||||
this.joined = true;
|
|
||||||
$(document).trigger('joined.muc', [from, member]);
|
|
||||||
UI.onMucJoined(from, member);
|
|
||||||
this.list_members.push(from);
|
|
||||||
}
|
|
||||||
} else if (this.members[from] === undefined) {
|
|
||||||
// new participant
|
|
||||||
this.members[from] = member;
|
|
||||||
this.list_members.push(from);
|
|
||||||
console.log('entered', from, member);
|
|
||||||
if (member.isFocus)
|
|
||||||
{
|
|
||||||
focusMucJid = from;
|
|
||||||
console.info("Ignore focus: " + from +", real JID: " + member.jid);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var id = $(pres).find('>userID').text();
|
|
||||||
var email = $(pres).find('>email');
|
|
||||||
if (email.length > 0) {
|
|
||||||
id = email.text();
|
|
||||||
}
|
|
||||||
UI.onMucEntered(from, id, member.displayName);
|
|
||||||
API.triggerEvent("participantJoined",{jid: from});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Presence update for existing participant
|
|
||||||
// Watch role change:
|
|
||||||
if (this.members[from].role != member.role) {
|
|
||||||
this.members[from].role = member.role;
|
|
||||||
UI.onMucRoleChanged(member.role, member.displayName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Always trigger presence to update bindings
|
|
||||||
$(document).trigger('presence.muc', [from, member, pres]);
|
|
||||||
|
|
||||||
// Trigger status message update
|
|
||||||
if (member.status) {
|
|
||||||
UI.onMucPresenceStatus(from, member);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
onPresenceUnavailable: function (pres) {
|
|
||||||
var from = pres.getAttribute('from');
|
|
||||||
// Status code 110 indicates that this notification is "self-presence".
|
|
||||||
if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
|
|
||||||
delete this.members[from];
|
|
||||||
this.list_members.splice(this.list_members.indexOf(from), 1);
|
|
||||||
this.onParticipantLeft(from);
|
|
||||||
}
|
|
||||||
// If the status code is 110 this means we're leaving and we would like
|
|
||||||
// to remove everyone else from our view, so we trigger the event.
|
|
||||||
else if (this.list_members.length > 1) {
|
|
||||||
for (var i = 0; i < this.list_members.length; i++) {
|
|
||||||
var member = this.list_members[i];
|
|
||||||
delete this.members[i];
|
|
||||||
this.list_members.splice(i, 1);
|
|
||||||
this.onParticipantLeft(member);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
|
|
||||||
$(document).trigger('kicked.muc', [from]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
onPresenceError: function (pres) {
|
|
||||||
var from = pres.getAttribute('from');
|
|
||||||
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
|
||||||
console.log('on password required', from);
|
|
||||||
|
|
||||||
UI.onPasswordReqiured(function (value) {
|
|
||||||
connection.emuc.doJoin(from, value);
|
|
||||||
})
|
|
||||||
} else if ($(pres).find(
|
|
||||||
'>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
|
||||||
var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
|
|
||||||
if(toDomain === config.hosts.anonymousdomain) {
|
|
||||||
// we are connected with anonymous domain and only non anonymous users can create rooms
|
|
||||||
// we must authorize the user
|
|
||||||
$(document).trigger('passwordrequired.main');
|
|
||||||
} else {
|
|
||||||
console.warn('onPresError ', pres);
|
|
||||||
UI.messageHandler.openReportDialog(null,
|
|
||||||
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
|
||||||
pres);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.warn('onPresError ', pres);
|
|
||||||
UI.messageHandler.openReportDialog(null,
|
|
||||||
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
|
||||||
pres);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
sendMessage: function (body, nickname) {
|
|
||||||
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
|
||||||
msg.c('body', body).up();
|
|
||||||
if (nickname) {
|
|
||||||
msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
|
|
||||||
}
|
|
||||||
this.connection.send(msg);
|
|
||||||
API.triggerEvent("outgoingMessage", {"message": body});
|
|
||||||
},
|
|
||||||
setSubject: function (subject){
|
|
||||||
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
|
||||||
msg.c('subject', subject);
|
|
||||||
this.connection.send(msg);
|
|
||||||
console.log("topic changed to " + subject);
|
|
||||||
},
|
|
||||||
onMessage: function (msg) {
|
|
||||||
// FIXME: this is a hack. but jingle on muc makes nickchanges hard
|
|
||||||
var from = msg.getAttribute('from');
|
|
||||||
var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);
|
|
||||||
|
|
||||||
var txt = $(msg).find('>body').text();
|
|
||||||
var type = msg.getAttribute("type");
|
|
||||||
if(type == "error")
|
|
||||||
{
|
|
||||||
UI.chatAddError($(msg).find('>text').text(), txt);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
var subject = $(msg).find('>subject');
|
|
||||||
if(subject.length)
|
|
||||||
{
|
|
||||||
var subjectText = subject.text();
|
|
||||||
if(subjectText || subjectText == "") {
|
|
||||||
UI.chatSetSubject(subjectText);
|
|
||||||
console.log("Subject is changed to " + subjectText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (txt) {
|
|
||||||
console.log('chat', nick, txt);
|
|
||||||
UI.updateChatConversation(from, nick, txt);
|
|
||||||
if(from != this.myroomjid)
|
|
||||||
API.triggerEvent("incomingMessage",
|
|
||||||
{"from": from, "nick": nick, "message": txt});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
lockRoom: function (key, onSuccess, onError, onNotSupported) {
|
|
||||||
//http://xmpp.org/extensions/xep-0045.html#roomconfig
|
|
||||||
var ob = this;
|
|
||||||
this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
|
|
||||||
function (res) {
|
|
||||||
if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
|
|
||||||
var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
|
||||||
formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
|
||||||
formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
|
||||||
formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
|
|
||||||
// Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
|
|
||||||
formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
|
|
||||||
// FIXME: is muc#roomconfig_passwordprotectedroom required?
|
|
||||||
this.connection.sendIQ(formsubmit,
|
|
||||||
onSuccess,
|
|
||||||
onError);
|
|
||||||
} else {
|
|
||||||
onNotSupported();
|
|
||||||
}
|
|
||||||
}, onError);
|
|
||||||
},
|
|
||||||
kick: function (jid) {
|
|
||||||
var kickIQ = $iq({to: this.roomjid, type: 'set'})
|
|
||||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
|
|
||||||
.c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
|
|
||||||
.c('reason').t('You have been kicked.').up().up().up();
|
|
||||||
|
|
||||||
this.connection.sendIQ(
|
|
||||||
kickIQ,
|
|
||||||
function (result) {
|
|
||||||
console.log('Kick participant with jid: ', jid, result);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('Kick participant error: ', error);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
sendPresence: function () {
|
|
||||||
var pres = $pres({to: this.presMap['to'] });
|
|
||||||
pres.c('x', {xmlns: this.presMap['xns']});
|
|
||||||
|
|
||||||
if (this.presMap['password']) {
|
|
||||||
pres.c('password').t(this.presMap['password']).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
pres.up();
|
|
||||||
|
|
||||||
// Send XEP-0115 'c' stanza that contains our capabilities info
|
|
||||||
if (connection.caps) {
|
|
||||||
connection.caps.node = config.clientNode;
|
|
||||||
pres.c('c', connection.caps.generateCapsAttrs()).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
|
|
||||||
.t(navigator.userAgent).up();
|
|
||||||
|
|
||||||
if(this.presMap['bridgeIsDown']) {
|
|
||||||
pres.c('bridgeIsDown').up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.presMap['email']) {
|
|
||||||
pres.c('email').t(this.presMap['email']).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.presMap['userId']) {
|
|
||||||
pres.c('userId').t(this.presMap['userId']).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.presMap['displayName']) {
|
|
||||||
// XEP-0172
|
|
||||||
pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
|
|
||||||
.t(this.presMap['displayName']).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.presMap['audions']) {
|
|
||||||
pres.c('audiomuted', {xmlns: this.presMap['audions']})
|
|
||||||
.t(this.presMap['audiomuted']).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.presMap['videons']) {
|
|
||||||
pres.c('videomuted', {xmlns: this.presMap['videons']})
|
|
||||||
.t(this.presMap['videomuted']).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if(this.presMap['statsns'])
|
|
||||||
{
|
|
||||||
var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
|
|
||||||
for(var stat in this.presMap["stats"])
|
|
||||||
if(this.presMap["stats"][stat] != null)
|
|
||||||
stats.c("stat",{name: stat, value: this.presMap["stats"][stat]}).up();
|
|
||||||
pres.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.presMap['prezins']) {
|
|
||||||
pres.c('prezi',
|
|
||||||
{xmlns: this.presMap['prezins'],
|
|
||||||
'url': this.presMap['preziurl']})
|
|
||||||
.c('current').t(this.presMap['prezicurrent']).up().up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.presMap['etherpadns']) {
|
|
||||||
pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
|
|
||||||
.t(this.presMap['etherpadname']).up();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.presMap['medians'])
|
|
||||||
{
|
|
||||||
pres.c('media', {xmlns: this.presMap['medians']});
|
|
||||||
var sourceNumber = 0;
|
|
||||||
Object.keys(this.presMap).forEach(function (key) {
|
|
||||||
if (key.indexOf('source') >= 0) {
|
|
||||||
sourceNumber++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (sourceNumber > 0)
|
|
||||||
for (var i = 1; i <= sourceNumber/3; i ++) {
|
|
||||||
pres.c('source',
|
|
||||||
{type: this.presMap['source' + i + '_type'],
|
|
||||||
ssrc: this.presMap['source' + i + '_ssrc'],
|
|
||||||
direction: this.presMap['source'+ i + '_direction']
|
|
||||||
|| 'sendrecv' }
|
|
||||||
).up();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pres.up();
|
|
||||||
// console.debug(pres.toString());
|
|
||||||
connection.send(pres);
|
|
||||||
},
|
|
||||||
addDisplayNameToPresence: function (displayName) {
|
|
||||||
this.presMap['displayName'] = displayName;
|
|
||||||
},
|
|
||||||
addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
|
|
||||||
if (!this.presMap['medians'])
|
|
||||||
this.presMap['medians'] = 'http://estos.de/ns/mjs';
|
|
||||||
|
|
||||||
this.presMap['source' + sourceNumber + '_type'] = mtype;
|
|
||||||
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
|
|
||||||
this.presMap['source' + sourceNumber + '_direction'] = direction;
|
|
||||||
},
|
|
||||||
clearPresenceMedia: function () {
|
|
||||||
var self = this;
|
|
||||||
Object.keys(this.presMap).forEach( function(key) {
|
|
||||||
if(key.indexOf('source') != -1) {
|
|
||||||
delete self.presMap[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
addPreziToPresence: function (url, currentSlide) {
|
|
||||||
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
|
|
||||||
this.presMap['preziurl'] = url;
|
|
||||||
this.presMap['prezicurrent'] = currentSlide;
|
|
||||||
},
|
|
||||||
removePreziFromPresence: function () {
|
|
||||||
delete this.presMap['prezins'];
|
|
||||||
delete this.presMap['preziurl'];
|
|
||||||
delete this.presMap['prezicurrent'];
|
|
||||||
},
|
|
||||||
addCurrentSlideToPresence: function (currentSlide) {
|
|
||||||
this.presMap['prezicurrent'] = currentSlide;
|
|
||||||
},
|
|
||||||
getPrezi: function (roomjid) {
|
|
||||||
return this.preziMap[roomjid];
|
|
||||||
},
|
|
||||||
addEtherpadToPresence: function(etherpadName) {
|
|
||||||
this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
|
|
||||||
this.presMap['etherpadname'] = etherpadName;
|
|
||||||
},
|
|
||||||
addAudioInfoToPresence: function(isMuted) {
|
|
||||||
this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
|
|
||||||
this.presMap['audiomuted'] = isMuted.toString();
|
|
||||||
},
|
|
||||||
addVideoInfoToPresence: function(isMuted) {
|
|
||||||
this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
|
|
||||||
this.presMap['videomuted'] = isMuted.toString();
|
|
||||||
},
|
|
||||||
addConnectionInfoToPresence: function(stats) {
|
|
||||||
this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
|
|
||||||
this.presMap['stats'] = stats;
|
|
||||||
},
|
|
||||||
findJidFromResource: function(resourceJid) {
|
|
||||||
if(resourceJid &&
|
|
||||||
resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
|
|
||||||
return connection.emuc.myroomjid;
|
|
||||||
}
|
|
||||||
var peerJid = null;
|
|
||||||
Object.keys(this.members).some(function (jid) {
|
|
||||||
peerJid = jid;
|
|
||||||
return Strophe.getResourceFromJid(jid) === resourceJid;
|
|
||||||
});
|
|
||||||
return peerJid;
|
|
||||||
},
|
|
||||||
addBridgeIsDownToPresence: function() {
|
|
||||||
this.presMap['bridgeIsDown'] = true;
|
|
||||||
},
|
|
||||||
addEmailToPresence: function(email) {
|
|
||||||
this.presMap['email'] = email;
|
|
||||||
},
|
|
||||||
addUserIdToPresence: function(userId) {
|
|
||||||
this.presMap['userId'] = userId;
|
|
||||||
},
|
|
||||||
isModerator: function() {
|
|
||||||
return this.role === 'moderator';
|
|
||||||
},
|
|
||||||
getMemberRole: function(peerJid) {
|
|
||||||
if (this.members[peerJid]) {
|
|
||||||
return this.members[peerJid].role;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
onParticipantLeft: function (jid) {
|
|
||||||
UI.onMucLeft(jid);
|
|
||||||
|
|
||||||
API.triggerEvent("participantLeft",{jid: jid});
|
|
||||||
|
|
||||||
delete jid2Ssrc[jid];
|
|
||||||
|
|
||||||
connection.jingle.terminateByJid(jid);
|
|
||||||
|
|
||||||
if (connection.emuc.getPrezi(jid)) {
|
|
||||||
$(document).trigger('presentationremoved.muc',
|
|
||||||
[jid, connection.emuc.getPrezi(jid)]);
|
|
||||||
}
|
|
||||||
|
|
||||||
Moderator.onMucLeft(jid);
|
|
||||||
}
|
|
||||||
});
|
|
167
recording.js
167
recording.js
|
@ -1,167 +0,0 @@
|
||||||
/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
|
|
||||||
Toolbar, Util */
|
|
||||||
var Recording = (function (my) {
|
|
||||||
var recordingToken = null;
|
|
||||||
var recordingEnabled;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to use a jirecon component for recording, or use the videobridge
|
|
||||||
* through COLIBRI.
|
|
||||||
*/
|
|
||||||
var useJirecon = (typeof config.hosts.jirecon != "undefined");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The ID of the jirecon recording session. Jirecon generates it when we
|
|
||||||
* initially start recording, and it needs to be used in subsequent requests
|
|
||||||
* to jirecon.
|
|
||||||
*/
|
|
||||||
var jireconRid = null;
|
|
||||||
|
|
||||||
my.setRecordingToken = function (token) {
|
|
||||||
recordingToken = token;
|
|
||||||
};
|
|
||||||
|
|
||||||
my.setRecording = function (state, token, callback) {
|
|
||||||
if (useJirecon){
|
|
||||||
this.setRecordingJirecon(state, token, callback);
|
|
||||||
} else {
|
|
||||||
this.setRecordingColibri(state, token, callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
my.setRecordingJirecon = function (state, token, callback) {
|
|
||||||
if (state == recordingEnabled){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var iq = $iq({to: config.hosts.jirecon, type: 'set'})
|
|
||||||
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
|
|
||||||
action: state ? 'start' : 'stop',
|
|
||||||
mucjid: connection.emuc.roomjid});
|
|
||||||
if (!state){
|
|
||||||
iq.attrs({rid: jireconRid});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Start recording');
|
|
||||||
|
|
||||||
connection.sendIQ(
|
|
||||||
iq,
|
|
||||||
function (result) {
|
|
||||||
// TODO wait for an IQ with the real status, since this is
|
|
||||||
// provisional?
|
|
||||||
jireconRid = $(result).find('recording').attr('rid');
|
|
||||||
console.log('Recording ' + (state ? 'started' : 'stopped') +
|
|
||||||
'(jirecon)' + result);
|
|
||||||
recordingEnabled = state;
|
|
||||||
if (!state){
|
|
||||||
jireconRid = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(state);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.log('Failed to start recording, error: ', error);
|
|
||||||
callback(recordingEnabled);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sends a COLIBRI message which enables or disables (according to 'state')
|
|
||||||
// the recording on the bridge. Waits for the result IQ and calls 'callback'
|
|
||||||
// with the new recording state, according to the IQ.
|
|
||||||
my.setRecordingColibri = function (state, token, callback) {
|
|
||||||
var elem = $iq({to: focusMucJid, type: 'set'});
|
|
||||||
elem.c('conference', {
|
|
||||||
xmlns: 'http://jitsi.org/protocol/colibri'
|
|
||||||
});
|
|
||||||
elem.c('recording', {state: state, token: token});
|
|
||||||
|
|
||||||
connection.sendIQ(elem,
|
|
||||||
function (result) {
|
|
||||||
console.log('Set recording "', state, '". Result:', result);
|
|
||||||
var recordingElem = $(result).find('>conference>recording');
|
|
||||||
var newState = ('true' === recordingElem.attr('state'));
|
|
||||||
|
|
||||||
recordingEnabled = newState;
|
|
||||||
callback(newState);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
console.warn(error);
|
|
||||||
callback(recordingEnabled);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
my.toggleRecording = function () {
|
|
||||||
if (!Moderator.isModerator()) {
|
|
||||||
console.log(
|
|
||||||
'non-focus, or conference not yet organized:' +
|
|
||||||
' not enabling recording');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jirecon does not (currently) support a token.
|
|
||||||
if (!recordingToken && !useJirecon)
|
|
||||||
{
|
|
||||||
UI.messageHandler.openTwoButtonDialog(null,
|
|
||||||
'<h2>Enter recording token</h2>' +
|
|
||||||
'<input id="recordingToken" type="text" ' +
|
|
||||||
'placeholder="token" autofocus>',
|
|
||||||
false,
|
|
||||||
"Save",
|
|
||||||
function (e, v, m, f) {
|
|
||||||
if (v) {
|
|
||||||
var token = document.getElementById('recordingToken');
|
|
||||||
|
|
||||||
if (token.value) {
|
|
||||||
my.setRecordingToken(
|
|
||||||
Util.escapeHtml(token.value));
|
|
||||||
my.toggleRecording();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
function (event) {
|
|
||||||
document.getElementById('recordingToken').focus();
|
|
||||||
},
|
|
||||||
function () {}
|
|
||||||
);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var oldState = recordingEnabled;
|
|
||||||
UI.setRecordingButtonState(!oldState);
|
|
||||||
my.setRecording(!oldState,
|
|
||||||
recordingToken,
|
|
||||||
function (state) {
|
|
||||||
console.log("New recording state: ", state);
|
|
||||||
if (state === oldState)
|
|
||||||
{
|
|
||||||
// FIXME: new focus:
|
|
||||||
// this will not work when moderator changes
|
|
||||||
// during active session. Then it will assume that
|
|
||||||
// recording status has changed to true, but it might have
|
|
||||||
// been already true(and we only received actual status from
|
|
||||||
// the focus).
|
|
||||||
//
|
|
||||||
// SO we start with status null, so that it is initialized
|
|
||||||
// here and will fail only after second click, so if invalid
|
|
||||||
// token was used we have to press the button twice before
|
|
||||||
// current status will be fetched and token will be reset.
|
|
||||||
//
|
|
||||||
// Reliable way would be to return authentication error.
|
|
||||||
// Or status update when moderator connects.
|
|
||||||
// Or we have to stop recording session when current
|
|
||||||
// moderator leaves the room.
|
|
||||||
|
|
||||||
// Failed to change, reset the token because it might
|
|
||||||
// have been wrong
|
|
||||||
my.setRecordingToken(null);
|
|
||||||
}
|
|
||||||
// Update with returned status
|
|
||||||
UI.setRecordingButtonState(state);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return my;
|
|
||||||
}(Recording || {}));
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
var XMPPEvents = {
|
||||||
|
CONFERENCE_CERATED: "xmpp.conferenceCreated.jingle",
|
||||||
|
CALL_TERMINATED: "xmpp.callterminated.jingle",
|
||||||
|
CALL_INCOMING: "xmpp.callincoming.jingle",
|
||||||
|
DISPOSE_CONFERENCE: "xmpp.dispoce_confernce",
|
||||||
|
KICKED: "xmpp.kicked",
|
||||||
|
BRIDGE_DOWN: "xmpp.bridge_down",
|
||||||
|
USER_ID_CHANGED: "xmpp.user_id_changed",
|
||||||
|
CHANGED_STREAMS: "xmpp.changed_streams",
|
||||||
|
MUC_JOINED: "xmpp.muc_joined",
|
||||||
|
DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
|
||||||
|
REMOTE_STATS: "xmpp.remote_stats"
|
||||||
|
};
|
||||||
|
//module.exports = XMPPEvents;
|
Loading…
Reference in New Issue