Creates initial version of xmpp module.

This commit is contained in:
hristoterezov 2015-01-19 11:20:00 +02:00
parent ee94eca733
commit e4e66a03d7
56 changed files with 8946 additions and 3385 deletions

721
app.js
View File

@ -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
}

View File

@ -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]);
},
});

View File

@ -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">

View File

@ -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

5082
libs/modules/xmpp.bundle.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -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;
}
);
}
}
);

View File

@ -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;
}
});

View File

@ -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";
}
};

View File

@ -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);
}
});

View File

@ -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

View File

@ -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)
{ {

View File

@ -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();
}
} }
}; };

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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];

View File

@ -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();
} }
/** /**

View File

@ -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',

View File

@ -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');

View File

@ -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 {

View File

@ -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);
} }
/** /**

View File

@ -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);
} }
}; };

View File

@ -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;

View File

@ -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"});

View File

@ -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.

View File

@ -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
} }
}; };

View File

@ -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)

View File

@ -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();
} }
/** /**

View File

@ -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}

View File

@ -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
}; };
}; };

View File

@ -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);
} }

View File

@ -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);
} }
}; };

View File

@ -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;

View File

@ -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';
}
};

165
modules/xmpp/SDPDiffer.js Normal file
View File

@ -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;

349
modules/xmpp/SDPUtil.js Normal file
View File

@ -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;

View File

@ -262,3 +262,5 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) {
} }
}; };
module.exports = TraceablePeerConnection;

View File

@ -1,11 +1,10 @@
/* 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 focusUserJid;
var getNextTimeout = Util.createExpBackoffTimer(1000); var getNextTimeout = Util.createExpBackoffTimer(1000);
var getNextErrorTimeout = Util.createExpBackoffTimer(1000); var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
@ -16,32 +15,39 @@ var Moderator = (function (my) {
// service discovery. // service discovery.
var sipGatewayEnabled = config.hosts.call_control !== undefined; var sipGatewayEnabled = config.hosts.call_control !== undefined;
my.isModerator = function () { var Moderator = {
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 || {}));

152
modules/xmpp/recording.js Normal file
View File

@ -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;

View File

@ -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);
}
});
};

View File

@ -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;
}
});
};

View File

@ -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]);
}
});
};

View File

@ -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);
}
});
}

View File

@ -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;
}
);
}
}
);
};

View File

@ -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";
}
};
};

422
modules/xmpp/xmpp.js Normal file
View File

@ -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
View File

@ -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);
}
});

View File

@ -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 || {}));

View File

@ -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;