Creates initial version of xmpp module.
This commit is contained in:
parent
ee94eca733
commit
e4e66a03d7
721
app.js
721
app.js
|
@ -1,17 +1,8 @@
|
|||
/* jshint -W117 */
|
||||
/* 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 focusMucJid = null;
|
||||
var roomName = null;
|
||||
var ssrc2jid = {};
|
||||
var bridgeIsDown = false;
|
||||
//TODO: this array must be removed when firefox implement multistream support
|
||||
var notReceivedSSRCs = [];
|
||||
|
||||
|
@ -27,674 +18,12 @@ var ssrc2videoType = {};
|
|||
* @type {String}
|
||||
*/
|
||||
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() {
|
||||
|
||||
|
||||
RTC.addStreamListener(maybeDoJoin, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||
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();
|
||||
statistics.start();
|
||||
|
||||
Moderator.init();
|
||||
|
||||
// Set default desktop sharing method
|
||||
desktopsharing.init();
|
||||
});
|
||||
|
||||
$(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())
|
||||
API.dispose();
|
||||
});
|
||||
|
||||
function disposeConference(onUnload) {
|
||||
UI.onDisposeConference(onUnload);
|
||||
var handler = activecall;
|
||||
if (handler && handler.peerconnection) {
|
||||
// FIXME: probably removing streams is not required and close() should
|
||||
// be enough
|
||||
if (RTC.localAudio) {
|
||||
handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload);
|
||||
}
|
||||
if (RTC.localVideo) {
|
||||
handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload);
|
||||
}
|
||||
handler.peerconnection.close();
|
||||
}
|
||||
statistics.onDisposeConference(onUnload);
|
||||
activecall = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the style class of the element given by id.
|
||||
*/
|
||||
function buttonClick(id, classname) {
|
||||
$(id).toggleClass(classname); // add the class to the clicked element
|
||||
}
|
||||
|
|
17
estos_log.js
17
estos_log.js
|
@ -1,17 +0,0 @@
|
|||
/* global Strophe */
|
||||
Strophe.addConnectionPlugin('logger', {
|
||||
// logs raw stanzas and makes them available for download as JSON
|
||||
connection: null,
|
||||
log: [],
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
this.connection.rawInput = this.log_incoming.bind(this);
|
||||
this.connection.rawOutput = this.log_outgoing.bind(this);
|
||||
},
|
||||
log_incoming: function (stanza) {
|
||||
this.log.push([new Date().getTime(), 'incoming', stanza]);
|
||||
},
|
||||
log_outgoing: function (stanza) {
|
||||
this.log.push([new Date().getTime(), 'outgoing', stanza]);
|
||||
},
|
||||
});
|
16
index.html
16
index.html
|
@ -11,16 +11,10 @@
|
|||
<meta itemprop="image" content="/images/jitsilogo.png"/>
|
||||
<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="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.disco.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/rayo.js?v=1"></script>
|
||||
<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/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/StreamEventTypes.js?v=2"></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="libs/modules/simulcast.bundle.js?v=3"></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/statistics.bundle.js?v=1"></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="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="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="moderator.js?v=2"></script><!-- media stream -->
|
||||
<script src="tracking.js?v=1"></script><!-- tracking -->
|
||||
<script src="keyboard_shortcut.js?v=4"></script>
|
||||
<link rel="stylesheet" href="css/font.css?v=6"/>
|
||||
<link rel="stylesheet" href="css/toastr.css?v=1">
|
||||
|
|
|
@ -14,20 +14,20 @@ var KeyboardShortcut = (function(my) {
|
|||
77: {
|
||||
character: "M",
|
||||
id: "mutePopover",
|
||||
function: toggleAudio
|
||||
function: UI.toggleAudio
|
||||
},
|
||||
84: {
|
||||
character: "T",
|
||||
function: function() {
|
||||
if(!RTC.localAudio.isMuted()) {
|
||||
toggleAudio();
|
||||
UI.toggleAudio();
|
||||
}
|
||||
}
|
||||
},
|
||||
86: {
|
||||
character: "V",
|
||||
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(e.which === "T".charCodeAt(0)) {
|
||||
if(RTC.localAudio.isMuted()) {
|
||||
toggleAudio();
|
||||
UI.toggleAudio();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
103
libs/rayo.js
103
libs/rayo.js
|
@ -1,103 +0,0 @@
|
|||
/* jshint -W117 */
|
||||
Strophe.addConnectionPlugin('rayo',
|
||||
{
|
||||
RAYO_XMLNS: 'urn:xmpp:rayo:1',
|
||||
connection: null,
|
||||
init: function (conn)
|
||||
{
|
||||
this.connection = conn;
|
||||
if (this.connection.disco)
|
||||
{
|
||||
this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
|
||||
}
|
||||
|
||||
this.connection.addHandler(
|
||||
this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
|
||||
},
|
||||
onRayo: function (iq)
|
||||
{
|
||||
console.info("Rayo IQ", iq);
|
||||
},
|
||||
dial: function (to, from, roomName, roomPass)
|
||||
{
|
||||
var self = this;
|
||||
var req = $iq(
|
||||
{
|
||||
type: 'set',
|
||||
to: focusMucJid
|
||||
}
|
||||
);
|
||||
req.c('dial',
|
||||
{
|
||||
xmlns: this.RAYO_XMLNS,
|
||||
to: to,
|
||||
from: from
|
||||
});
|
||||
req.c('header',
|
||||
{
|
||||
name: 'JvbRoomName',
|
||||
value: roomName
|
||||
}).up();
|
||||
|
||||
if (roomPass !== null && roomPass.length) {
|
||||
|
||||
req.c('header',
|
||||
{
|
||||
name: 'JvbRoomPassword',
|
||||
value: roomPass
|
||||
}).up();
|
||||
}
|
||||
|
||||
this.connection.sendIQ(
|
||||
req,
|
||||
function (result)
|
||||
{
|
||||
console.info('Dial result ', result);
|
||||
|
||||
var resource = $(result).find('ref').attr('uri');
|
||||
this.call_resource = resource.substr('xmpp:'.length);
|
||||
console.info(
|
||||
"Received call resource: " + this.call_resource);
|
||||
},
|
||||
function (error)
|
||||
{
|
||||
console.info('Dial error ', error);
|
||||
}
|
||||
);
|
||||
},
|
||||
hang_up: function ()
|
||||
{
|
||||
if (!this.call_resource)
|
||||
{
|
||||
console.warn("No call in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var req = $iq(
|
||||
{
|
||||
type: 'set',
|
||||
to: this.call_resource
|
||||
}
|
||||
);
|
||||
req.c('hangup',
|
||||
{
|
||||
xmlns: this.RAYO_XMLNS
|
||||
});
|
||||
|
||||
this.connection.sendIQ(
|
||||
req,
|
||||
function (result)
|
||||
{
|
||||
console.info('Hangup result ', result);
|
||||
self.call_resource = null;
|
||||
},
|
||||
function (error)
|
||||
{
|
||||
console.info('Hangup error ', error);
|
||||
self.call_resource = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
|
@ -1,327 +0,0 @@
|
|||
/* jshint -W117 */
|
||||
|
||||
|
||||
function CallIncomingJingle(sid) {
|
||||
var sess = connection.jingle.sessions[sid];
|
||||
|
||||
// TODO: do we check activecall == null?
|
||||
activecall = sess;
|
||||
|
||||
statistics.onConferenceCreated(sess);
|
||||
RTC.onConferenceCreated(sess);
|
||||
|
||||
// TODO: check affiliation and/or role
|
||||
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
|
||||
sess.usedrip = true; // not-so-naive trickle ice
|
||||
sess.sendAnswer();
|
||||
sess.accept();
|
||||
|
||||
};
|
||||
|
||||
Strophe.addConnectionPlugin('jingle', {
|
||||
connection: null,
|
||||
sessions: {},
|
||||
jid2session: {},
|
||||
ice_config: {iceServers: []},
|
||||
pc_constraints: {},
|
||||
media_constraints: {
|
||||
mandatory: {
|
||||
'OfferToReceiveAudio': true,
|
||||
'OfferToReceiveVideo': true
|
||||
}
|
||||
// MozDontOfferDataChannel: true when this is firefox
|
||||
},
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
if (this.connection.disco) {
|
||||
// http://xmpp.org/extensions/xep-0167.html#support
|
||||
// http://xmpp.org/extensions/xep-0176.html#support
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
|
||||
|
||||
|
||||
// this is dealt with by SDP O/A so we don't need to annouce this
|
||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
|
||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
|
||||
if (config.useRtcpMux) {
|
||||
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
|
||||
}
|
||||
if (config.useBundle) {
|
||||
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
|
||||
}
|
||||
//this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
|
||||
}
|
||||
this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
|
||||
},
|
||||
onJingle: function (iq) {
|
||||
var sid = $(iq).find('jingle').attr('sid');
|
||||
var action = $(iq).find('jingle').attr('action');
|
||||
var fromJid = iq.getAttribute('from');
|
||||
// send ack first
|
||||
var ack = $iq({type: 'result',
|
||||
to: fromJid,
|
||||
id: iq.getAttribute('id')
|
||||
});
|
||||
console.log('on jingle ' + action + ' from ' + fromJid, iq);
|
||||
var sess = this.sessions[sid];
|
||||
if ('session-initiate' != action) {
|
||||
if (sess === null) {
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
|
||||
// local jid is not checked
|
||||
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
|
||||
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
} else if (sess !== undefined) {
|
||||
// existing session with same session id
|
||||
// this might be out-of-order if the sess.peerjid is the same as from
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
|
||||
console.warn('duplicate session id', sid);
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
// FIXME: check for a defined action
|
||||
this.connection.send(ack);
|
||||
// see http://xmpp.org/extensions/xep-0166.html#concepts-session
|
||||
switch (action) {
|
||||
case 'session-initiate':
|
||||
sess = new JingleSession($(iq).attr('to'), $(iq).find('jingle').attr('sid'), this.connection);
|
||||
// configure session
|
||||
|
||||
sess.media_constraints = this.media_constraints;
|
||||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(fromJid, false);
|
||||
// FIXME: setRemoteDescription should only be done when this call is to be accepted
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
|
||||
|
||||
this.sessions[sess.sid] = sess;
|
||||
this.jid2session[sess.peerjid] = sess;
|
||||
|
||||
// the callback should either
|
||||
// .sendAnswer and .accept
|
||||
// or .sendTerminate -- not necessarily synchronus
|
||||
CallIncomingJingle(sess.sid);
|
||||
break;
|
||||
case 'session-accept':
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
|
||||
sess.accept();
|
||||
$(document).trigger('callaccepted.jingle', [sess.sid]);
|
||||
break;
|
||||
case 'session-terminate':
|
||||
// If this is not the focus sending the terminate, we have
|
||||
// nothing more to do here.
|
||||
if (Object.keys(this.sessions).length < 1
|
||||
|| !(this.sessions[Object.keys(this.sessions)[0]]
|
||||
instanceof JingleSession))
|
||||
{
|
||||
break;
|
||||
}
|
||||
console.log('terminating...', sess.sid);
|
||||
sess.terminate();
|
||||
this.terminate(sess.sid);
|
||||
if ($(iq).find('>jingle>reason').length) {
|
||||
$(document).trigger('callterminated.jingle', [
|
||||
sess.sid,
|
||||
sess.peerjid,
|
||||
$(iq).find('>jingle>reason>:first')[0].tagName,
|
||||
$(iq).find('>jingle>reason>text').text()
|
||||
]);
|
||||
} else {
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, sess.peerjid]);
|
||||
}
|
||||
break;
|
||||
case 'transport-info':
|
||||
sess.addIceCandidate($(iq).find('>jingle>content'));
|
||||
break;
|
||||
case 'session-info':
|
||||
var affected;
|
||||
if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
$(document).trigger('ringing.jingle', [sess.sid]);
|
||||
} else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||
$(document).trigger('mute.jingle', [sess.sid, affected]);
|
||||
} else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||
$(document).trigger('unmute.jingle', [sess.sid, affected]);
|
||||
}
|
||||
break;
|
||||
case 'addsource': // FIXME: proprietary, un-jingleish
|
||||
case 'source-add': // FIXME: proprietary
|
||||
sess.addSource($(iq).find('>jingle>content'), fromJid);
|
||||
break;
|
||||
case 'removesource': // FIXME: proprietary, un-jingleish
|
||||
case 'source-remove': // FIXME: proprietary
|
||||
sess.removeSource($(iq).find('>jingle>content'), fromJid);
|
||||
break;
|
||||
default:
|
||||
console.warn('jingle action not implemented', action);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
|
||||
var sess = new JingleSession(myjid || this.connection.jid,
|
||||
Math.random().toString(36).substr(2, 12), // random string
|
||||
this.connection);
|
||||
// configure session
|
||||
|
||||
sess.media_constraints = this.media_constraints;
|
||||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(peerjid, true);
|
||||
this.sessions[sess.sid] = sess;
|
||||
this.jid2session[sess.peerjid] = sess;
|
||||
sess.sendOffer();
|
||||
return sess;
|
||||
},
|
||||
terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
|
||||
if (sid === null || sid === undefined) {
|
||||
for (sid in this.sessions) {
|
||||
if (this.sessions[sid].state != 'ended') {
|
||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||
this.sessions[sid].terminate();
|
||||
}
|
||||
delete this.jid2session[this.sessions[sid].peerjid];
|
||||
delete this.sessions[sid];
|
||||
}
|
||||
} else if (this.sessions.hasOwnProperty(sid)) {
|
||||
if (this.sessions[sid].state != 'ended') {
|
||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||
this.sessions[sid].terminate();
|
||||
}
|
||||
delete this.jid2session[this.sessions[sid].peerjid];
|
||||
delete this.sessions[sid];
|
||||
}
|
||||
},
|
||||
// Used to terminate a session when an unavailable presence is received.
|
||||
terminateByJid: function (jid) {
|
||||
if (this.jid2session.hasOwnProperty(jid)) {
|
||||
var sess = this.jid2session[jid];
|
||||
if (sess) {
|
||||
sess.terminate();
|
||||
console.log('peer went away silently', jid);
|
||||
delete this.sessions[sess.sid];
|
||||
delete this.jid2session[jid];
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, jid], 'gone');
|
||||
}
|
||||
}
|
||||
},
|
||||
terminateRemoteByJid: function (jid, reason) {
|
||||
if (this.jid2session.hasOwnProperty(jid)) {
|
||||
var sess = this.jid2session[jid];
|
||||
if (sess) {
|
||||
sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
|
||||
sess.terminate();
|
||||
console.log('terminate peer with jid', sess.sid, jid);
|
||||
delete this.sessions[sess.sid];
|
||||
delete this.jid2session[jid];
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, jid, 'kicked']);
|
||||
}
|
||||
}
|
||||
},
|
||||
getStunAndTurnCredentials: function () {
|
||||
// get stun and turn configuration from server via xep-0215
|
||||
// uses time-limited credentials as described in
|
||||
// http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
|
||||
//
|
||||
// see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
|
||||
// for a prosody module which implements this
|
||||
//
|
||||
// currently, this doesn't work with updateIce and therefore credentials with a long
|
||||
// validity have to be fetched before creating the peerconnection
|
||||
// TODO: implement refresh via updateIce as described in
|
||||
// https://code.google.com/p/webrtc/issues/detail?id=1650
|
||||
var self = this;
|
||||
this.connection.sendIQ(
|
||||
$iq({type: 'get', to: this.connection.domain})
|
||||
.c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
|
||||
function (res) {
|
||||
var iceservers = [];
|
||||
$(res).find('>services>service').each(function (idx, el) {
|
||||
el = $(el);
|
||||
var dict = {};
|
||||
var type = el.attr('type');
|
||||
switch (type) {
|
||||
case 'stun':
|
||||
dict.url = 'stun:' + el.attr('host');
|
||||
if (el.attr('port')) {
|
||||
dict.url += ':' + el.attr('port');
|
||||
}
|
||||
iceservers.push(dict);
|
||||
break;
|
||||
case 'turn':
|
||||
case 'turns':
|
||||
dict.url = type + ':';
|
||||
if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
|
||||
if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
|
||||
dict.url += el.attr('username') + '@';
|
||||
} else {
|
||||
dict.username = el.attr('username'); // only works in M28
|
||||
}
|
||||
}
|
||||
dict.url += el.attr('host');
|
||||
if (el.attr('port') && el.attr('port') != '3478') {
|
||||
dict.url += ':' + el.attr('port');
|
||||
}
|
||||
if (el.attr('transport') && el.attr('transport') != 'udp') {
|
||||
dict.url += '?transport=' + el.attr('transport');
|
||||
}
|
||||
if (el.attr('password')) {
|
||||
dict.credential = el.attr('password');
|
||||
}
|
||||
iceservers.push(dict);
|
||||
break;
|
||||
}
|
||||
});
|
||||
self.ice_config.iceServers = iceservers;
|
||||
},
|
||||
function (err) {
|
||||
console.warn('getting turn credentials failed', err);
|
||||
console.warn('is mod_turncredentials or similar installed?');
|
||||
}
|
||||
);
|
||||
// implement push?
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates the log data
|
||||
*/
|
||||
populateData: function () {
|
||||
var data = {};
|
||||
Object.keys(this.sessions).forEach(function (sid) {
|
||||
var session = this.sessions[sid];
|
||||
if (session.peerconnection && session.peerconnection.updateLog) {
|
||||
// FIXME: should probably be a .dump call
|
||||
data["jingle_" + session.sid] = {
|
||||
updateLog: session.peerconnection.updateLog,
|
||||
stats: session.peerconnection.stats,
|
||||
url: window.location.href
|
||||
};
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* Strophe logger implementation. Logs from level WARN and above.
|
||||
*/
|
||||
Strophe.log = function (level, msg) {
|
||||
switch(level) {
|
||||
case Strophe.LogLevel.WARN:
|
||||
console.warn("Strophe: "+msg);
|
||||
break;
|
||||
case Strophe.LogLevel.ERROR:
|
||||
case Strophe.LogLevel.FATAL:
|
||||
console.error("Strophe: "+msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Strophe.getStatusString = function(status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case Strophe.Status.ERROR:
|
||||
return "ERROR";
|
||||
case Strophe.Status.CONNECTING:
|
||||
return "CONNECTING";
|
||||
case Strophe.Status.CONNFAIL:
|
||||
return "CONNFAIL";
|
||||
case Strophe.Status.AUTHENTICATING:
|
||||
return "AUTHENTICATING";
|
||||
case Strophe.Status.AUTHFAIL:
|
||||
return "AUTHFAIL";
|
||||
case Strophe.Status.CONNECTED:
|
||||
return "CONNECTED";
|
||||
case Strophe.Status.DISCONNECTED:
|
||||
return "DISCONNECTED";
|
||||
case Strophe.Status.DISCONNECTING:
|
||||
return "DISCONNECTING";
|
||||
case Strophe.Status.ATTACHED:
|
||||
return "ATTACHED";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
};
|
|
@ -1,56 +0,0 @@
|
|||
/* global $, $iq, config, connection, focusMucJid, forceMuted,
|
||||
setAudioMuted, Strophe, toggleAudio */
|
||||
/**
|
||||
* Moderate connection plugin.
|
||||
*/
|
||||
Strophe.addConnectionPlugin('moderate', {
|
||||
connection: null,
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
|
||||
this.connection.addHandler(this.onMute.bind(this),
|
||||
'http://jitsi.org/jitmeet/audio',
|
||||
'iq',
|
||||
'set',
|
||||
null,
|
||||
null);
|
||||
},
|
||||
setMute: function (jid, mute) {
|
||||
console.info("set mute", mute);
|
||||
var iqToFocus = $iq({to: focusMucJid, type: 'set'})
|
||||
.c('mute', {
|
||||
xmlns: 'http://jitsi.org/jitmeet/audio',
|
||||
jid: jid
|
||||
})
|
||||
.t(mute.toString())
|
||||
.up();
|
||||
|
||||
this.connection.sendIQ(
|
||||
iqToFocus,
|
||||
function (result) {
|
||||
console.log('set mute', result);
|
||||
},
|
||||
function (error) {
|
||||
console.log('set mute error', error);
|
||||
});
|
||||
},
|
||||
onMute: function (iq) {
|
||||
var from = iq.getAttribute('from');
|
||||
if (from !== focusMucJid) {
|
||||
console.warn("Ignored mute from non focus peer");
|
||||
return false;
|
||||
}
|
||||
var mute = $(iq).find('mute');
|
||||
if (mute.length) {
|
||||
var doMuteAudio = mute.text() === "true";
|
||||
setAudioMuted(doMuteAudio);
|
||||
forceMuted = doMuteAudio;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
eject: function (jid) {
|
||||
// We're not the focus, so can't terminate
|
||||
//connection.jingle.terminateRemoteByJid(jid, 'kick');
|
||||
connection.emuc.kick(jid);
|
||||
}
|
||||
});
|
|
@ -18,8 +18,8 @@
|
|||
var commands =
|
||||
{
|
||||
displayName: UI.inputDisplayNameHandler,
|
||||
muteAudio: toggleAudio,
|
||||
muteVideo: toggleVideo,
|
||||
muteAudio: UI.toggleAudio,
|
||||
muteVideo: UI.toggleVideo,
|
||||
toggleFilmStrip: UI.toggleFilmStrip,
|
||||
toggleChat: UI.toggleChat,
|
||||
toggleContactList: UI.toggleContactList
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global connection, Strophe, updateLargeVideo, focusedVideoSrc*/
|
||||
/* global Strophe, updateLargeVideo, focusedVideoSrc*/
|
||||
|
||||
// cache datachannels to avoid garbage collection
|
||||
// https://code.google.com/p/chromium/issues/detail?id=405545
|
||||
|
@ -91,7 +91,7 @@ var DataChannels =
|
|||
newValue = new Boolean(newValue).valueOf();
|
||||
}
|
||||
}
|
||||
$(document).trigger('inlastnchanged', [oldValue, newValue]);
|
||||
UI.onLastNChanged(oldValue, newValue);
|
||||
}
|
||||
else if ("LastNEndpointsChangeEvent" === colibriClass)
|
||||
{
|
||||
|
|
|
@ -58,7 +58,7 @@ var RTC = {
|
|||
createRemoteStream: function (data, sid, thessrc) {
|
||||
var remoteStream = new MediaStream(data, sid, thessrc, eventEmitter,
|
||||
this.getBrowserType());
|
||||
var jid = data.peerjid || connection.emuc.myroomjid;
|
||||
var jid = data.peerjid || xmpp.myJid();
|
||||
if(!this.remoteStreams[jid]) {
|
||||
this.remoteStreams[jid] = {};
|
||||
}
|
||||
|
@ -144,16 +144,7 @@ var RTC = {
|
|||
RTC.localVideo = this.createLocalStream(stream, type, true);
|
||||
// Stop the stream to trigger onended event for old stream
|
||||
oldStream.stop();
|
||||
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();
|
||||
}
|
||||
xmpp.switchStreams(stream, oldStream,callback);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
260
modules/UI/UI.js
260
modules/UI/UI.js
|
@ -17,9 +17,11 @@ var PanelToggler = require("./side_pannels/SidePanelToggler");
|
|||
var RoomNameGenerator = require("./welcome_page/RoomnameGenerator");
|
||||
UI.messageHandler = require("./util/MessageHandler");
|
||||
var messageHandler = UI.messageHandler;
|
||||
var Authentication = require("./authentication/Authentication");
|
||||
var UIUtil = require("./util/UIUtil");
|
||||
|
||||
//var eventEmitter = new EventEmitter();
|
||||
|
||||
var roomName = null;
|
||||
|
||||
|
||||
function setupPrezi()
|
||||
|
@ -39,7 +41,7 @@ function setupChat()
|
|||
}
|
||||
|
||||
function setupToolbars() {
|
||||
Toolbar.init();
|
||||
Toolbar.init(UI);
|
||||
Toolbar.setupButtonsFromConfig();
|
||||
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() {
|
||||
RTC.addStreamListener(streamHandler, StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||
|
||||
|
@ -70,14 +82,7 @@ function registerListeners() {
|
|||
VideoLayout.onRemoteStreamAdded(stream);
|
||||
}, StreamEventTypes.EVENT_TYPE_REMOTE_CREATED);
|
||||
|
||||
// Listen for large video size updates
|
||||
document.getElementById('largeVideo')
|
||||
.addEventListener('loadedmetadata', function (e) {
|
||||
currentVideoWidth = this.videoWidth;
|
||||
currentVideoHeight = this.videoHeight;
|
||||
VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight);
|
||||
});
|
||||
|
||||
VideoLayout.init();
|
||||
|
||||
statistics.addAudioLevelListener(function(jid, audioLevel)
|
||||
{
|
||||
|
@ -104,8 +109,38 @@ function registerListeners() {
|
|||
desktopsharing.addListener(
|
||||
Toolbar.changeDesktopSharingButtonState,
|
||||
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()
|
||||
|
@ -117,10 +152,6 @@ function bindEvents()
|
|||
function () {
|
||||
VideoLayout.resizeLargeVideoContainer();
|
||||
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 () {
|
||||
Chat.toggleSmileys();
|
||||
};
|
||||
|
@ -278,7 +304,7 @@ UI.updateChatConversation = function (from, displayName, message) {
|
|||
return Chat.updateChatConversation(from, displayName, message);
|
||||
};
|
||||
|
||||
UI.onMucJoined = function (jid, info) {
|
||||
function onMucJoined(jid, info) {
|
||||
Toolbar.updateRoomUrl(window.location.href);
|
||||
document.getElementById('localNick').appendChild(
|
||||
document.createTextNode(Strophe.getResourceFromJid(jid) + ' (me)')
|
||||
|
@ -293,15 +319,14 @@ UI.onMucJoined = function (jid, info) {
|
|||
|
||||
// Show authenticate button if needed
|
||||
Toolbar.showAuthenticateButton(
|
||||
Moderator.isExternalAuthEnabled() && !Moderator.isModerator());
|
||||
xmpp.isExternalAuthEnabled() && !xmpp.isModerator());
|
||||
|
||||
var displayName = !config.displayJids
|
||||
? info.displayName : Strophe.getResourceFromJid(jid);
|
||||
|
||||
if (displayName)
|
||||
$(document).trigger('displaynamechanged',
|
||||
['localVideoContainer', displayName + ' (me)']);
|
||||
};
|
||||
onDisplayNameChanged('localVideoContainer', displayName + ' (me)');
|
||||
}
|
||||
|
||||
UI.initEtherpad = function (name) {
|
||||
Etherpad.init(name);
|
||||
|
@ -357,23 +382,19 @@ UI.toggleContactList = function () {
|
|||
UI.onLocalRoleChange = function (jid, info, pres) {
|
||||
|
||||
console.info("My role changed, new role: " + info.role);
|
||||
var isModerator = Moderator.isModerator();
|
||||
var isModerator = xmpp.isModerator();
|
||||
|
||||
VideoLayout.showModeratorIndicator();
|
||||
Toolbar.showAuthenticateButton(
|
||||
Moderator.isExternalAuthEnabled() && !isModerator);
|
||||
xmpp.isExternalAuthEnabled() && !isModerator);
|
||||
|
||||
if (isModerator) {
|
||||
Toolbar.closeAuthenticationWindow();
|
||||
Authentication.closeAuthenticationWindow();
|
||||
messageHandler.notify(
|
||||
'Me', 'connected', 'Moderator rights granted !');
|
||||
}
|
||||
};
|
||||
|
||||
UI.onDisposeConference = function (unload) {
|
||||
Toolbar.showAuthenticateButton(false);
|
||||
};
|
||||
|
||||
UI.onModeratorStatusChanged = function (isModerator) {
|
||||
|
||||
Toolbar.showSipCallButton(isModerator);
|
||||
|
@ -414,40 +435,11 @@ UI.onPasswordReqiured = function (callback) {
|
|||
);
|
||||
};
|
||||
|
||||
UI.onAuthenticationRequired = function () {
|
||||
// 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(
|
||||
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();
|
||||
}
|
||||
}
|
||||
);
|
||||
UI.onAuthenticationRequired = function (intervalCallback) {
|
||||
Authentication.openAuthenticationDialog(
|
||||
roomName, intervalCallback, function () {
|
||||
Toolbar.authenticateClicked();
|
||||
});
|
||||
};
|
||||
|
||||
UI.setRecordingButtonState = function (state) {
|
||||
|
@ -511,6 +503,8 @@ UI.showLocalAudioIndicator = function (mute) {
|
|||
};
|
||||
|
||||
UI.generateRoomName = function() {
|
||||
if(roomName)
|
||||
return roomName;
|
||||
var roomnode = null;
|
||||
var path = window.location.pathname;
|
||||
|
||||
|
@ -540,6 +534,7 @@ UI.generateRoomName = function() {
|
|||
}
|
||||
|
||||
roomName = roomnode + '@' + config.hosts.muc;
|
||||
return roomName;
|
||||
};
|
||||
|
||||
|
||||
|
@ -556,25 +551,146 @@ UI.dockToolbar = function (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) {
|
||||
elem = elem.parentNode;
|
||||
elem.download = filename || 'meetlog.json';
|
||||
elem.href = 'data:application/json;charset=utf-8,\n';
|
||||
var data = {};
|
||||
if (connection.jingle) {
|
||||
data = connection.jingle.populateData();
|
||||
}
|
||||
var data = xmpp.populateData();
|
||||
var metadata = {};
|
||||
metadata.time = new Date();
|
||||
metadata.url = window.location.href;
|
||||
metadata.ua = navigator.userAgent;
|
||||
if (connection.logger) {
|
||||
metadata.xmpp = connection.logger.log;
|
||||
var log = xmpp.getLogger();
|
||||
if (log) {
|
||||
metadata.xmpp = log;
|
||||
}
|
||||
data.metadata = metadata;
|
||||
elem.href += encodeURIComponent(JSON.stringify(data, null, ' '));
|
||||
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;
|
||||
|
||||
|
|
|
@ -87,10 +87,10 @@ var AudioLevels = (function(my) {
|
|||
drawContext.drawImage(canvasCache, 0, 0);
|
||||
|
||||
if(resourceJid === AudioLevels.LOCAL_LEVEL) {
|
||||
if(!connection.emuc.myroomjid) {
|
||||
if(!xmpp.myJid()) {
|
||||
return;
|
||||
}
|
||||
resourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
||||
resourceJid = xmpp.myResource();
|
||||
}
|
||||
|
||||
if(resourceJid === largeVideoResourceJid) {
|
||||
|
@ -221,8 +221,8 @@ var AudioLevels = (function(my) {
|
|||
function getVideoSpanId(resourceJid) {
|
||||
var videoSpanId = null;
|
||||
if (resourceJid === AudioLevels.LOCAL_LEVEL
|
||||
|| (connection.emuc.myroomjid && resourceJid
|
||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)))
|
||||
|| (xmpp.myResource() && resourceJid
|
||||
=== xmpp.myResource()))
|
||||
videoSpanId = 'localVideoContainer';
|
||||
else
|
||||
videoSpanId = 'participant_' + resourceJid;
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
/* Initial "authentication required" dialog */
|
||||
var authDialog = null;
|
||||
/* Loop retry ID that wits for other user to create the room */
|
||||
var authRetryId = null;
|
||||
var authenticationWindow = null;
|
||||
|
||||
var Authentication = {
|
||||
openAuthenticationDialog: function (roomName, intervalCallback, callback) {
|
||||
// This is the loop that will wait for the room to be created by
|
||||
// someone else. 'auth_required.moderator' will bring us back here.
|
||||
authRetryId = window.setTimeout(intervalCallback , 5000);
|
||||
// Show prompt only if it's not open
|
||||
if (authDialog !== null) {
|
||||
return;
|
||||
}
|
||||
// extract room name from 'room@muc.server.net'
|
||||
var room = roomName.substr(0, roomName.indexOf('@'));
|
||||
|
||||
authDialog = messageHandler.openDialog(
|
||||
'Stop',
|
||||
'Authentication is required to create room:<br/><b>' + room +
|
||||
'</b></br> You can either authenticate to create the room or ' +
|
||||
'just wait for someone else to do so.',
|
||||
true,
|
||||
{
|
||||
Authenticate: 'authNow'
|
||||
},
|
||||
function (onSubmitEvent, submitValue) {
|
||||
|
||||
// Do not close the dialog yet
|
||||
onSubmitEvent.preventDefault();
|
||||
|
||||
// Open login popup
|
||||
if (submitValue === 'authNow') {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
closeAuthenticationWindow:function () {
|
||||
if (authenticationWindow) {
|
||||
authenticationWindow.close();
|
||||
authenticationWindow = null;
|
||||
}
|
||||
},
|
||||
focusAuthenticationWindow: function () {
|
||||
// If auth window exists just bring it to the front
|
||||
if (authenticationWindow) {
|
||||
authenticationWindow.focus();
|
||||
return;
|
||||
}
|
||||
},
|
||||
closeAuthenticationDialog: function () {
|
||||
// Close authentication dialog if opened
|
||||
if (authDialog) {
|
||||
UI.messageHandler.closeDialog();
|
||||
authDialog = null;
|
||||
}
|
||||
},
|
||||
createAuthenticationWindow: function (callback, url) {
|
||||
authenticationWindow = messageHandler.openCenteredPopup(
|
||||
url, 910, 660,
|
||||
// On closed
|
||||
function () {
|
||||
// Close authentication dialog if opened
|
||||
if (authDialog) {
|
||||
messageHandler.closeDialog();
|
||||
authDialog = null;
|
||||
}
|
||||
callback();
|
||||
authenticationWindow = null;
|
||||
});
|
||||
return authenticationWindow;
|
||||
},
|
||||
stopInterval: function () {
|
||||
// Clear retry interval, so that we don't call 'doJoinAfterFocus' twice
|
||||
if (authRetryId) {
|
||||
window.clearTimeout(authRetryId);
|
||||
authRetryId = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = Authentication;
|
|
@ -12,7 +12,7 @@ function setVisibility(selector, show) {
|
|||
function isUserMuted(jid) {
|
||||
// XXX(gp) we may want to rename this method to something like
|
||||
// isUserStreaming, for example.
|
||||
if (jid && jid != connection.emuc.myroomjid) {
|
||||
if (jid && jid != xmpp.myJid()) {
|
||||
var resource = Strophe.getResourceFromJid(jid);
|
||||
if (!require("../videolayout/VideoLayout").isInLastN(resource)) {
|
||||
return true;
|
||||
|
@ -26,7 +26,7 @@ function isUserMuted(jid) {
|
|||
}
|
||||
|
||||
function getGravatarUrl(id, size) {
|
||||
if(id === connection.emuc.myroomjid || !id) {
|
||||
if(id === xmpp.myJid() || !id) {
|
||||
id = Settings.getSettings().uid;
|
||||
}
|
||||
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
|
||||
// local video container
|
||||
if (jid === connection.emuc.myroomjid) {
|
||||
if (jid === xmpp.myJid()) {
|
||||
$('#avatar').get(0).src = thumbUrl;
|
||||
thumbnail = $('#localVideoContainer');
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ var Avatar = {
|
|||
var video = $('#participant_' + resourceJid + '>video');
|
||||
var avatar = $('#avatar_' + resourceJid);
|
||||
|
||||
if (jid === connection.emuc.myroomjid) {
|
||||
if (jid === xmpp.myJid()) {
|
||||
video = $('#localVideoWrapper>video');
|
||||
}
|
||||
if (show === undefined || show === null) {
|
||||
|
@ -130,7 +130,7 @@ var Avatar = {
|
|||
*/
|
||||
updateActiveSpeakerAvatarSrc: function (jid) {
|
||||
if (!jid) {
|
||||
jid = connection.emuc.findJidFromResource(
|
||||
jid = xmpp.findJidFromResource(
|
||||
require("../videolayout/VideoLayout").getLargeVideoState().userResourceJid);
|
||||
}
|
||||
var avatar = $("#activeSpeakerAvatar")[0];
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $, config, connection, dockToolbar, Moderator,
|
||||
/* global $, config, dockToolbar,
|
||||
setLargeVideoVisible, Util */
|
||||
|
||||
var VideoLayout = require("../videolayout/VideoLayout");
|
||||
|
@ -30,8 +30,7 @@ function resize() {
|
|||
* Shares the Etherpad name with other participants.
|
||||
*/
|
||||
function shareEtherpad() {
|
||||
connection.emuc.addEtherpadToPresence(etherpadName);
|
||||
connection.emuc.sendPresence();
|
||||
xmpp.addToPresence("etherpad", etherpadName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -30,7 +30,7 @@ var Prezi = {
|
|||
* to load.
|
||||
*/
|
||||
openPreziDialog: function() {
|
||||
var myprezi = connection.emuc.getPrezi(connection.emuc.myroomjid);
|
||||
var myprezi = xmpp.getPrezi();
|
||||
if (myprezi) {
|
||||
messageHandler.openTwoButtonDialog("Remove Prezi",
|
||||
"Are you sure you would like to remove your Prezi?",
|
||||
|
@ -38,8 +38,7 @@ var Prezi = {
|
|||
"Remove",
|
||||
function(e,v,m,f) {
|
||||
if(v) {
|
||||
connection.emuc.removePreziFromPresence();
|
||||
connection.emuc.sendPresence();
|
||||
xmpp.removePreziFromPresence();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -91,9 +90,7 @@ var Prezi = {
|
|||
return false;
|
||||
}
|
||||
else {
|
||||
connection.emuc
|
||||
.addPreziToPresence(urlValue, 0);
|
||||
connection.emuc.sendPresence();
|
||||
xmpp.addToPresence("prezi", urlValue);
|
||||
$.prompt.close();
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +148,7 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
|
|||
VideoLayout.resizeThumbnails();
|
||||
|
||||
var controlsEnabled = false;
|
||||
if (jid === connection.emuc.myroomjid)
|
||||
if (jid === xmpp.myJid())
|
||||
controlsEnabled = true;
|
||||
|
||||
setPresentationVisible(true);
|
||||
|
@ -191,15 +188,14 @@ function presentationAdded(event, jid, presUrl, currentSlide) {
|
|||
preziPlayer.on(PreziPlayer.EVENT_STATUS, function(event) {
|
||||
console.log("prezi status", event.value);
|
||||
if (event.value == PreziPlayer.STATUS_CONTENT_READY) {
|
||||
if (jid != connection.emuc.myroomjid)
|
||||
if (jid != xmpp.myJid())
|
||||
preziPlayer.flyToStep(currentSlide);
|
||||
}
|
||||
});
|
||||
|
||||
preziPlayer.on(PreziPlayer.EVENT_CURRENT_STEP, function(event) {
|
||||
console.log("event value", event.value);
|
||||
connection.emuc.addCurrentSlideToPresence(event.value);
|
||||
connection.emuc.sendPresence();
|
||||
xmpp.addToPresence("preziSlide", event.value);
|
||||
});
|
||||
|
||||
$("#" + elementId).css( 'background-image',
|
||||
|
|
|
@ -4,6 +4,7 @@ var Settings = require("./settings/Settings");
|
|||
var SettingsMenu = require("./settings/SettingsMenu");
|
||||
var VideoLayout = require("../videolayout/VideoLayout");
|
||||
var ToolbarToggler = require("../toolbars/ToolbarToggler");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
var toggle = function(object, selector, onOpenComplete, onOpen, onClose) {
|
||||
buttonClick(buttons[selector], "active");
|
||||
UIUtil.buttonClick(buttons[selector], "active");
|
||||
|
||||
if (object.isVisible()) {
|
||||
$("#toast-container").animate({
|
||||
|
@ -140,7 +141,7 @@ var PanelToggler = (function(my) {
|
|||
|
||||
if(currentlyOpen) {
|
||||
var current = $(currentlyOpen);
|
||||
buttonClick(buttons[currentlyOpen], "active");
|
||||
UIUtil.buttonClick(buttons[currentlyOpen], "active");
|
||||
current.css('z-index', 4);
|
||||
setTimeout(function () {
|
||||
current.css('display', 'none');
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global $, Util, connection, nickname:true, showToolbar */
|
||||
/* global $, Util, nickname:true, showToolbar */
|
||||
var Replacement = require("./Replacement");
|
||||
var CommandsProcessor = require("./Commands");
|
||||
var ToolbarToggler = require("../../toolbars/ToolbarToggler");
|
||||
|
@ -184,8 +184,7 @@ var Chat = (function (my) {
|
|||
nickname = val;
|
||||
window.localStorage.displayname = nickname;
|
||||
|
||||
connection.emuc.addDisplayNameToPresence(nickname);
|
||||
connection.emuc.sendPresence();
|
||||
xmpp.addToPresence("displayName", nickname);
|
||||
|
||||
Chat.setChatConversationMode(true);
|
||||
|
||||
|
@ -208,7 +207,7 @@ var Chat = (function (my) {
|
|||
else
|
||||
{
|
||||
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) {
|
||||
var divClassName = '';
|
||||
|
||||
if (connection.emuc.myroomjid === from) {
|
||||
if (xmpp.myJid() === from) {
|
||||
divClassName = "localuser";
|
||||
}
|
||||
else {
|
||||
|
|
|
@ -32,7 +32,7 @@ function getCommand(message)
|
|||
function processTopic(commandArguments)
|
||||
{
|
||||
var topic = Util.escapeHtml(commandArguments);
|
||||
connection.emuc.setSubject(topic);
|
||||
xmpp.setSubject(topic);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -46,23 +46,6 @@ function createDisplayNameParagraph(displayName) {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* Indicates that the display name has changed.
|
||||
*/
|
||||
$(document).bind( 'displaynamechanged',
|
||||
function (event, peerJid, displayName) {
|
||||
if (peerJid === 'localVideoContainer')
|
||||
peerJid = connection.emuc.myroomjid;
|
||||
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactName = $('#contactlist #' + resourceJid + '>p');
|
||||
|
||||
if (contactName && displayName && displayName.length > 0)
|
||||
contactName.html(displayName);
|
||||
});
|
||||
|
||||
|
||||
function stopGlowing(glower) {
|
||||
window.clearInterval(notificationInterval);
|
||||
notificationInterval = false;
|
||||
|
@ -127,7 +110,7 @@ var ContactList = {
|
|||
|
||||
var clElement = contactlist.get(0);
|
||||
|
||||
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)
|
||||
if (resourceJid === xmpp.myResource()
|
||||
&& $('#contactlist>ul .title')[0].nextSibling.nextSibling) {
|
||||
clElement.insertBefore(newContact,
|
||||
$('#contactlist>ul .title')[0].nextSibling.nextSibling);
|
||||
|
@ -182,6 +165,18 @@ var ContactList = {
|
|||
} else {
|
||||
contact.removeClass('clickable');
|
||||
}
|
||||
},
|
||||
|
||||
onDisplayNameChange: function (peerJid, displayName) {
|
||||
if (peerJid === 'localVideoContainer')
|
||||
peerJid = xmpp.myJid();
|
||||
|
||||
var resourceJid = Strophe.getResourceFromJid(peerJid);
|
||||
|
||||
var contactName = $('#contactlist #' + resourceJid + '>p');
|
||||
|
||||
if (contactName && displayName && displayName.length > 0)
|
||||
contactName.html(displayName);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -10,16 +10,15 @@ var SettingsMenu = {
|
|||
|
||||
if(newDisplayName) {
|
||||
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);
|
||||
|
||||
|
||||
connection.emuc.sendPresence();
|
||||
Avatar.setUserAvatar(connection.emuc.myroomjid, email);
|
||||
Avatar.setUserAvatar(xmpp.myJid(), email);
|
||||
},
|
||||
|
||||
isVisible: function() {
|
||||
|
@ -29,14 +28,15 @@ var SettingsMenu = {
|
|||
setDisplayName: function(newDisplayName) {
|
||||
var displayName = Settings.setDisplayName(newDisplayName);
|
||||
$('#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;
|
|
@ -1,22 +1,24 @@
|
|||
/* global $, buttonClick, config, lockRoom, Moderator, roomName,
|
||||
setSharedKey, sharedKey, Util */
|
||||
/* global $, buttonClick, config, lockRoom,
|
||||
setSharedKey, Util */
|
||||
var messageHandler = require("../util/MessageHandler");
|
||||
var BottomToolbar = require("./BottomToolbar");
|
||||
var Prezi = require("../prezi/Prezi");
|
||||
var Etherpad = require("../etherpad/Etherpad");
|
||||
var PanelToggler = require("../side_pannels/SidePanelToggler");
|
||||
var Authentication = require("../authentication/Authentication");
|
||||
var UIUtil = require("../util/UIUtil");
|
||||
|
||||
var roomUrl = null;
|
||||
var sharedKey = '';
|
||||
var authenticationWindow = null;
|
||||
var UI = null;
|
||||
|
||||
var buttonHandlers =
|
||||
{
|
||||
"toolbar_button_mute": function () {
|
||||
return toggleAudio();
|
||||
return UI.toggleAudio();
|
||||
},
|
||||
"toolbar_button_camera": function () {
|
||||
return toggleVideo();
|
||||
return UI.toggleVideo();
|
||||
},
|
||||
"toolbar_button_authentication": function () {
|
||||
return Toolbar.authenticateClicked();
|
||||
|
@ -44,7 +46,7 @@ var buttonHandlers =
|
|||
},
|
||||
"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();
|
||||
},
|
||||
"toolbar_button_sip": function () {
|
||||
|
@ -59,9 +61,7 @@ var buttonHandlers =
|
|||
};
|
||||
|
||||
function hangup() {
|
||||
disposeConference();
|
||||
sessionTerminated = true;
|
||||
connection.emuc.doLeave();
|
||||
xmpp.disposeConference();
|
||||
if(config.enableWelcomePage)
|
||||
{
|
||||
setTimeout(function()
|
||||
|
@ -90,7 +90,29 @@ function hangup() {
|
|||
*/
|
||||
|
||||
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)
|
||||
currentSharedKey = sharedKey;
|
||||
|
||||
connection.emuc.lockRoom(currentSharedKey, function (res) {
|
||||
xmpp.lockRoom(currentSharedKey, function (res) {
|
||||
// password is required
|
||||
if (sharedKey)
|
||||
{
|
||||
|
@ -183,9 +205,8 @@ function callSipButtonClicked()
|
|||
if (v) {
|
||||
var numberInput = document.getElementById('sipNumber');
|
||||
if (numberInput.value) {
|
||||
connection.rayo.dial(
|
||||
numberInput.value, 'fromnumber',
|
||||
roomName, sharedKey);
|
||||
xmpp.dial(numberInput.value, 'fromnumber',
|
||||
UI.getRoomName(), sharedKey);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -197,9 +218,10 @@ function callSipButtonClicked()
|
|||
|
||||
var Toolbar = (function (my) {
|
||||
|
||||
my.init = function () {
|
||||
my.init = function (ui) {
|
||||
for(var k in buttonHandlers)
|
||||
$("#" + k).click(buttonHandlers[k]);
|
||||
UI = ui;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -210,35 +232,15 @@ var Toolbar = (function (my) {
|
|||
sharedKey = sKey;
|
||||
};
|
||||
|
||||
my.closeAuthenticationWindow = function () {
|
||||
if (authenticationWindow) {
|
||||
authenticationWindow.close();
|
||||
authenticationWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
my.authenticateClicked = function () {
|
||||
// If auth window exists just bring it to the front
|
||||
if (authenticationWindow) {
|
||||
authenticationWindow.focus();
|
||||
return;
|
||||
}
|
||||
Authentication.focusAuthenticationWindow();
|
||||
// Get authentication URL
|
||||
Moderator.getAuthUrl(function (url) {
|
||||
xmpp.getAuthUrl(UI.getRoomName(), function (url) {
|
||||
// Open popup with authentication URL
|
||||
authenticationWindow = messageHandler.openCenteredPopup(
|
||||
url, 910, 660,
|
||||
// On closed
|
||||
function () {
|
||||
// Close authentication dialog if opened
|
||||
if (authDialog) {
|
||||
messageHandler.closeDialog();
|
||||
authDialog = null;
|
||||
}
|
||||
// On popup closed - retry room allocation
|
||||
Moderator.allocateConferenceFocus(roomName, doJoinAfterFocus);
|
||||
authenticationWindow = null;
|
||||
});
|
||||
var authenticationWindow = Authentication.createAuthenticationWindow(function () {
|
||||
// On popup closed - retry room allocation
|
||||
xmpp.allocateConferenceFocus(UI.getRoomName(), UI.checkForNicknameAndJoin);
|
||||
}, url);
|
||||
if (!authenticationWindow) {
|
||||
Toolbar.showAuthenticateButton(true);
|
||||
messageHandler.openMessageDialog(
|
||||
|
@ -279,7 +281,7 @@ var Toolbar = (function (my) {
|
|||
*/
|
||||
my.openLockDialog = function () {
|
||||
// Only the focus is able to set a shared key.
|
||||
if (!Moderator.isModerator()) {
|
||||
if (!xmpp.isModerator()) {
|
||||
if (sharedKey) {
|
||||
messageHandler.openMessageDialog(null,
|
||||
"This conversation is currently protected by" +
|
||||
|
@ -436,14 +438,14 @@ var Toolbar = (function (my) {
|
|||
*/
|
||||
my.unlockLockButton = function () {
|
||||
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.
|
||||
*/
|
||||
my.lockLockButton = function () {
|
||||
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
|
||||
my.showSipCallButton = function (show) {
|
||||
if (Moderator.isSipGatewayEnabled() && show) {
|
||||
if (xmpp.isSipGatewayEnabled() && show) {
|
||||
$('#sipCallButton').css({display: "inline"});
|
||||
} else {
|
||||
$('#sipCallButton').css({display: "none"});
|
||||
|
|
|
@ -67,7 +67,7 @@ var ToolbarToggler = {
|
|||
toolbarTimeout = interfaceConfig.TOOLBAR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (Moderator.isModerator())
|
||||
if (xmpp.isModerator())
|
||||
{
|
||||
// TODO: Enable settings functionality.
|
||||
// Need to uncomment the settings button in index.html.
|
||||
|
|
|
@ -11,6 +11,13 @@ module.exports = {
|
|||
= PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0;
|
||||
|
||||
return window.innerWidth - rightPanelWidth;
|
||||
},
|
||||
/**
|
||||
* Changes the style class of the element given by id.
|
||||
*/
|
||||
buttonClick: function(id, classname) {
|
||||
$(id).toggleClass(classname); // add the class to the clicked element
|
||||
}
|
||||
|
||||
|
||||
};
|
|
@ -16,8 +16,99 @@ var largeVideoState = {
|
|||
newSrc: ''
|
||||
};
|
||||
|
||||
/**
|
||||
* 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";
|
||||
|
||||
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,
|
||||
* so that if fits its parent.
|
||||
|
@ -194,7 +285,7 @@ function getParticipantContainer(resourceJid)
|
|||
if (!resourceJid)
|
||||
return null;
|
||||
|
||||
if (resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
||||
if (resourceJid === xmpp.myResource())
|
||||
return $("#localVideoContainer");
|
||||
else
|
||||
return $("#participant_" + resourceJid);
|
||||
|
@ -270,7 +361,8 @@ function addRemoteVideoMenu(jid, parentElement) {
|
|||
event.preventDefault();
|
||||
}
|
||||
var isMute = mutedAudios[jid] == true;
|
||||
connection.moderate.setMute(jid, !isMute);
|
||||
xmpp.setMute(jid, !isMute);
|
||||
|
||||
popupmenuElement.setAttribute('style', 'display:none;');
|
||||
|
||||
if (isMute) {
|
||||
|
@ -292,7 +384,7 @@ function addRemoteVideoMenu(jid, parentElement) {
|
|||
var ejectLinkItem = document.createElement('a');
|
||||
ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
|
||||
ejectLinkItem.onclick = function(){
|
||||
connection.moderate.eject(jid);
|
||||
xmpp.eject(jid);
|
||||
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) {
|
||||
my.connectionIndicators = {};
|
||||
|
||||
|
@ -407,6 +536,16 @@ var VideoLayout = (function (my) {
|
|||
my.getVideoSize = getCameraVideoSize;
|
||||
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) {
|
||||
return lastNCount < 0 // lastN is disabled, 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').volume = 0;
|
||||
if (preMuted) {
|
||||
setAudioMuted(true);
|
||||
if(!UI.setAudioMuted(true))
|
||||
{
|
||||
preMuted = mute;
|
||||
}
|
||||
preMuted = false;
|
||||
}
|
||||
};
|
||||
|
@ -459,14 +601,14 @@ var VideoLayout = (function (my) {
|
|||
VideoLayout.handleVideoThumbClicked(
|
||||
RTC.getVideoSrc(localVideo),
|
||||
false,
|
||||
Strophe.getResourceFromJid(connection.emuc.myroomjid));
|
||||
xmpp.myResource());
|
||||
});
|
||||
$('#localVideoContainer').click(function (event) {
|
||||
event.stopPropagation();
|
||||
VideoLayout.handleVideoThumbClicked(
|
||||
RTC.getVideoSrc(localVideo),
|
||||
false,
|
||||
Strophe.getResourceFromJid(connection.emuc.myroomjid));
|
||||
xmpp.myResource());
|
||||
});
|
||||
|
||||
// Add hover handler
|
||||
|
@ -496,11 +638,8 @@ var VideoLayout = (function (my) {
|
|||
|
||||
localVideoSrc = RTC.getVideoSrc(localVideo);
|
||||
|
||||
var myResourceJid = null;
|
||||
if(connection.emuc.myroomjid)
|
||||
{
|
||||
myResourceJid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
||||
}
|
||||
var myResourceJid = xmpp.myResource();
|
||||
|
||||
VideoLayout.updateLargeVideo(localVideoSrc, 0,
|
||||
myResourceJid);
|
||||
|
||||
|
@ -539,7 +678,7 @@ var VideoLayout = (function (my) {
|
|||
{
|
||||
if(container.id == "localVideoWrapper")
|
||||
{
|
||||
jid = Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
||||
jid = xmpp.myResource();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -617,9 +756,9 @@ var VideoLayout = (function (my) {
|
|||
largeVideoState.isVisible = $('#largeVideo').is(':visible');
|
||||
largeVideoState.isDesktop = isVideoSrcDesktop(resourceJid);
|
||||
if(jid2Ssrc[largeVideoState.userResourceJid] ||
|
||||
(connection && connection.emuc.myroomjid &&
|
||||
(xmpp.myResource() &&
|
||||
largeVideoState.userResourceJid ===
|
||||
Strophe.getResourceFromJid(connection.emuc.myroomjid))) {
|
||||
xmpp.myResource())) {
|
||||
largeVideoState.oldResourceJid = largeVideoState.userResourceJid;
|
||||
} else {
|
||||
largeVideoState.oldResourceJid = null;
|
||||
|
@ -643,7 +782,7 @@ var VideoLayout = (function (my) {
|
|||
var doUpdate = function () {
|
||||
|
||||
Avatar.updateActiveSpeakerAvatarSrc(
|
||||
connection.emuc.findJidFromResource(
|
||||
xmpp.findJidFromResource(
|
||||
largeVideoState.userResourceJid));
|
||||
|
||||
if (!userChanged && largeVideoState.preload &&
|
||||
|
@ -723,7 +862,7 @@ var VideoLayout = (function (my) {
|
|||
|
||||
if(userChanged) {
|
||||
Avatar.showUserAvatar(
|
||||
connection.emuc.findJidFromResource(
|
||||
xmpp.findJidFromResource(
|
||||
largeVideoState.oldResourceJid));
|
||||
}
|
||||
|
||||
|
@ -738,7 +877,7 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
} else {
|
||||
Avatar.showUserAvatar(
|
||||
connection.emuc.findJidFromResource(
|
||||
xmpp.findJidFromResource(
|
||||
largeVideoState.userResourceJid));
|
||||
}
|
||||
|
||||
|
@ -877,7 +1016,7 @@ var VideoLayout = (function (my) {
|
|||
focusedVideoInfo = null;
|
||||
if(focusResourceJid) {
|
||||
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
|
||||
// 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);
|
||||
|
||||
remotes.appendChild(container);
|
||||
|
@ -1134,13 +1273,13 @@ var VideoLayout = (function (my) {
|
|||
if (state == 'show')
|
||||
{
|
||||
// peerContainer.css('-webkit-filter', '');
|
||||
var jid = connection.emuc.findJidFromResource(resourceJid);
|
||||
var jid = xmpp.findJidFromResource(resourceJid);
|
||||
Avatar.showUserAvatar(jid, false);
|
||||
}
|
||||
else // if (state == 'avatar')
|
||||
{
|
||||
// peerContainer.css('-webkit-filter', 'grayscale(100%)');
|
||||
var jid = connection.emuc.findJidFromResource(resourceJid);
|
||||
var jid = xmpp.findJidFromResource(resourceJid);
|
||||
Avatar.showUserAvatar(jid, true);
|
||||
}
|
||||
}
|
||||
|
@ -1166,8 +1305,7 @@ var VideoLayout = (function (my) {
|
|||
if (name && nickname !== name) {
|
||||
nickname = name;
|
||||
window.localStorage.displayname = nickname;
|
||||
connection.emuc.addDisplayNameToPresence(nickname);
|
||||
connection.emuc.sendPresence();
|
||||
xmpp.addToPresence("displayName", nickname);
|
||||
|
||||
Chat.setChatConversationMode(true);
|
||||
}
|
||||
|
@ -1238,7 +1376,7 @@ var VideoLayout = (function (my) {
|
|||
*/
|
||||
my.showModeratorIndicator = function () {
|
||||
|
||||
var isModerator = Moderator.isModerator();
|
||||
var isModerator = xmpp.isModerator();
|
||||
if (isModerator) {
|
||||
var indicatorSpan = $('#localVideoContainer .focusindicator');
|
||||
|
||||
|
@ -1247,7 +1385,10 @@ var VideoLayout = (function (my) {
|
|||
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') {
|
||||
// Skip server side focus
|
||||
|
@ -1263,7 +1404,7 @@ var VideoLayout = (function (my) {
|
|||
return;
|
||||
}
|
||||
|
||||
var member = connection.emuc.members[jid];
|
||||
var member = members[jid];
|
||||
|
||||
if (member.role === 'moderator') {
|
||||
// Remove menu if peer is moderator
|
||||
|
@ -1435,7 +1576,7 @@ var VideoLayout = (function (my) {
|
|||
var videoSpanId = null;
|
||||
var videoContainerId = null;
|
||||
if (resourceJid
|
||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
|
||||
=== xmpp.myResource()) {
|
||||
videoSpanId = 'localVideoWrapper';
|
||||
videoContainerId = 'localVideoContainer';
|
||||
}
|
||||
|
@ -1478,7 +1619,7 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
|
||||
Avatar.showUserAvatar(
|
||||
connection.emuc.findJidFromResource(resourceJid));
|
||||
xmpp.findJidFromResource(resourceJid));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1603,7 +1744,7 @@ var VideoLayout = (function (my) {
|
|||
lastNPickupJid = jid;
|
||||
$(document).trigger("pinnedendpointchanged", [jid]);
|
||||
}
|
||||
} else if (jid == connection.emuc.myroomjid) {
|
||||
} else if (jid == xmpp.myJid()) {
|
||||
$("#localVideoContainer").click();
|
||||
}
|
||||
}
|
||||
|
@ -1615,13 +1756,13 @@ var VideoLayout = (function (my) {
|
|||
$(document).bind('audiomuted.muc', function (event, jid, isMuted) {
|
||||
/*
|
||||
// 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
|
||||
return;
|
||||
}*/
|
||||
var videoSpanId = null;
|
||||
if (jid === connection.emuc.myroomjid) {
|
||||
if (jid === xmpp.myJid()) {
|
||||
videoSpanId = 'localVideoContainer';
|
||||
} else {
|
||||
VideoLayout.ensurePeerContainerExists(jid);
|
||||
|
@ -1630,7 +1771,7 @@ var VideoLayout = (function (my) {
|
|||
|
||||
mutedAudios[jid] = isMuted;
|
||||
|
||||
if (Moderator.isModerator()) {
|
||||
if (xmpp.isModerator()) {
|
||||
VideoLayout.updateRemoteVideoMenu(jid, isMuted);
|
||||
}
|
||||
|
||||
|
@ -1648,7 +1789,7 @@ var VideoLayout = (function (my) {
|
|||
|
||||
Avatar.showUserAvatar(jid, isMuted);
|
||||
var videoSpanId = null;
|
||||
if (jid === connection.emuc.myroomjid) {
|
||||
if (jid === xmpp.myJid()) {
|
||||
videoSpanId = 'localVideoContainer';
|
||||
} else {
|
||||
VideoLayout.ensurePeerContainerExists(jid);
|
||||
|
@ -1662,11 +1803,11 @@ var VideoLayout = (function (my) {
|
|||
/**
|
||||
* Display name changed.
|
||||
*/
|
||||
$(document).bind('displaynamechanged',
|
||||
function (event, jid, displayName, status) {
|
||||
my.onDisplayNameChanged =
|
||||
function (jid, displayName, status) {
|
||||
var name = null;
|
||||
if (jid === 'localVideoContainer'
|
||||
|| jid === connection.emuc.myroomjid) {
|
||||
|| jid === xmpp.myJid()) {
|
||||
name = nickname;
|
||||
setDisplayName('localVideoContainer',
|
||||
displayName);
|
||||
|
@ -1680,10 +1821,10 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
|
||||
if(jid === 'localVideoContainer')
|
||||
jid = connection.emuc.myroomjid;
|
||||
jid = xmpp.myJid();
|
||||
if(!name || name != displayName)
|
||||
API.triggerEvent("displayNameChange",{jid: jid, displayname: displayName});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* On dominant speaker changed event.
|
||||
|
@ -1691,7 +1832,7 @@ var VideoLayout = (function (my) {
|
|||
$(document).bind('dominantspeakerchanged', function (event, resourceJid) {
|
||||
// We ignore local user events.
|
||||
if (resourceJid
|
||||
=== Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
||||
=== xmpp.myResource())
|
||||
return;
|
||||
|
||||
// Update the current dominant speaker.
|
||||
|
@ -1822,7 +1963,7 @@ var VideoLayout = (function (my) {
|
|||
if (!isVisible) {
|
||||
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 sel = $('#participant_' + resourceJid + '>video');
|
||||
|
||||
|
@ -1855,7 +1996,7 @@ var VideoLayout = (function (my) {
|
|||
|
||||
var resource, container, src;
|
||||
var myResource
|
||||
= Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
||||
= xmpp.myResource();
|
||||
|
||||
// Find out which endpoint to show in the large video.
|
||||
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) {
|
||||
endpointSimulcastLayers.forEach(function (esl) {
|
||||
|
||||
|
@ -1930,13 +2040,13 @@ var VideoLayout = (function (my) {
|
|||
|
||||
// Get session and stream from primary ssrc.
|
||||
var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
|
||||
var session = res.session;
|
||||
var sid = res.sid;
|
||||
var electedStream = res.stream;
|
||||
|
||||
if (session && electedStream) {
|
||||
if (sid && electedStream) {
|
||||
var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
|
||||
|
||||
console.info([esl, primarySSRC, msid, session, electedStream]);
|
||||
console.info([esl, primarySSRC, msid, sid, electedStream]);
|
||||
|
||||
var msidParts = msid.split(' ');
|
||||
|
||||
|
@ -1956,7 +2066,7 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
|
||||
} 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.
|
||||
var res = simulcast.getReceivingVideoStreamBySSRC(primarySSRC);
|
||||
var session = res.session;
|
||||
var sid = res.sid;
|
||||
var electedStream = res.stream;
|
||||
|
||||
if (session && electedStream) {
|
||||
if (sid && electedStream) {
|
||||
var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
|
||||
|
||||
console.info('Switching simulcast substream.');
|
||||
console.info([esl, primarySSRC, msid, session, electedStream]);
|
||||
console.info([esl, primarySSRC, msid, sid, electedStream]);
|
||||
|
||||
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])
|
||||
== largeVideoState.userResourceJid);
|
||||
|
@ -2035,7 +2145,7 @@ var VideoLayout = (function (my) {
|
|||
}
|
||||
|
||||
var videoId;
|
||||
if(resource == Strophe.getResourceFromJid(connection.emuc.myroomjid))
|
||||
if(resource == xmpp.myResource())
|
||||
{
|
||||
videoId = "localVideoContainer";
|
||||
}
|
||||
|
@ -2048,7 +2158,7 @@ var VideoLayout = (function (my) {
|
|||
connectionIndicator.updatePopoverData();
|
||||
|
||||
} 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)
|
||||
{
|
||||
resolution = object.resolution;
|
||||
object.resolution = resolution[connection.emuc.myroomjid];
|
||||
delete resolution[connection.emuc.myroomjid];
|
||||
object.resolution = resolution[xmpp.myJid()];
|
||||
delete resolution[xmpp.myJid()];
|
||||
}
|
||||
updateStatsIndicator("localVideoContainer", percent, object);
|
||||
for(var jid in resolution)
|
||||
|
|
|
@ -29,8 +29,7 @@ function startSendingStats() {
|
|||
* Sends statistics to other participants
|
||||
*/
|
||||
function sendStats() {
|
||||
connection.emuc.addConnectionInfoToPresence(convertToMUCStats(stats));
|
||||
connection.emuc.sendPresence();
|
||||
xmpp.addToPresence("connectionQuality", convertToMUCStats(stats));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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).
|
||||
* @type {boolean}
|
||||
|
|
|
@ -159,43 +159,19 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
|
|||
|
||||
// If we haven't receiving a "changed" event yet, then we must be receiving
|
||||
// low quality (that the sender always streams).
|
||||
if (!ssrc && connection.jingle) {
|
||||
var session;
|
||||
var i, j, k;
|
||||
|
||||
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();
|
||||
if (tracks) {
|
||||
for (k = 0; k < tracks.length; k++) {
|
||||
var track = tracks[k];
|
||||
var msid = [remoteStream.id, track.id].join(' ');
|
||||
var _ssrc = this._remoteMaps.msid2ssrc[msid];
|
||||
var _jid = ssrc2jid[_ssrc];
|
||||
var quality = this._remoteMaps.msid2Quality[msid];
|
||||
if (jid == _jid && quality == 0) {
|
||||
ssrc = _ssrc;
|
||||
// stream found, stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!ssrc)
|
||||
{
|
||||
var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||
var remoteStream = remoteStreamObject.getOriginalStream();
|
||||
var tracks = remoteStream.getVideoTracks();
|
||||
if (tracks) {
|
||||
for (var k = 0; k < tracks.length; k++) {
|
||||
var track = tracks[k];
|
||||
var msid = [remoteStream.id, track.id].join(' ');
|
||||
var _ssrc = this._remoteMaps.msid2ssrc[msid];
|
||||
var quality = this._remoteMaps.msid2Quality[msid];
|
||||
if (quality == 0) {
|
||||
ssrc = _ssrc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -206,47 +182,32 @@ SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
|
|||
|
||||
SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
|
||||
{
|
||||
var session, electedStream;
|
||||
var sid, electedStream;
|
||||
var i, j, k;
|
||||
if (connection.jingle) {
|
||||
var keys = Object.keys(connection.jingle.sessions);
|
||||
for (i = 0; i < keys.length; i++) {
|
||||
var sid = keys[i];
|
||||
|
||||
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();
|
||||
if (tracks) {
|
||||
for (k = 0; k < tracks.length; k++) {
|
||||
var track = tracks[k];
|
||||
var msid = [remoteStream.id, track.id].join(' ');
|
||||
var tmp = this._remoteMaps.msid2ssrc[msid];
|
||||
if (tmp == ssrc) {
|
||||
electedStream = new webkitMediaStream([track]);
|
||||
// stream found, stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
var jid = ssrc2jid[ssrc];
|
||||
if(jid)
|
||||
{
|
||||
var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
|
||||
var remoteStream = remoteStreamObject.getOriginalStream();
|
||||
var tracks = remoteStream.getVideoTracks();
|
||||
if (tracks) {
|
||||
for (k = 0; k < tracks.length; k++) {
|
||||
var track = tracks[k];
|
||||
var msid = [remoteStream.id, track.id].join(' ');
|
||||
var tmp = this._remoteMaps.msid2ssrc[msid];
|
||||
if (tmp == ssrc) {
|
||||
electedStream = new webkitMediaStream([track]);
|
||||
sid = remoteStreamObject.sid;
|
||||
// stream found, stop.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
session: session,
|
||||
sid: sid,
|
||||
stream: electedStream
|
||||
};
|
||||
};
|
||||
|
|
|
@ -329,30 +329,9 @@ StatsCollector.prototype.addStatsToBeLogged = function (reports) {
|
|||
};
|
||||
|
||||
StatsCollector.prototype.logStats = function () {
|
||||
if (!focusMucJid) {
|
||||
|
||||
if(!xmpp.sendLogs(this.statsToBeLogged))
|
||||
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
|
||||
this.statsToBeLogged.stats = {};
|
||||
this.statsToBeLogged.timestamps = [];
|
||||
|
@ -700,7 +679,7 @@ StatsCollector.prototype.processAudioLevelReport = function ()
|
|||
// but it seems to vary between 0 and around 32k.
|
||||
audioLevel = audioLevel / 32767;
|
||||
jidStats.setSsrcAudioLevel(ssrc, audioLevel);
|
||||
if(jid != connection.emuc.myroomjid)
|
||||
if(jid != xmpp.myJid())
|
||||
this.eventEmitter.emit("statistics.audioLevel", jid, audioLevel);
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,14 @@ function onStreamCreated(stream)
|
|||
localStats.start();
|
||||
}
|
||||
|
||||
function onDisposeConference(onUnload) {
|
||||
stopRemote();
|
||||
if(onUnload) {
|
||||
stopLocal();
|
||||
eventEmitter.removeAllListeners();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var statistics =
|
||||
{
|
||||
|
@ -117,19 +125,12 @@ var statistics =
|
|||
startRemoteStats(event.peerconnection);
|
||||
},
|
||||
|
||||
onDisposeConference: function (onUnload) {
|
||||
stopRemote();
|
||||
if(onUnload) {
|
||||
stopLocal();
|
||||
eventEmitter.removeAllListeners();
|
||||
}
|
||||
},
|
||||
|
||||
start: function () {
|
||||
this.addConnectionStatsListener(connectionquality.updateLocalStats);
|
||||
this.addRemoteStatsStopListener(connectionquality.stopSendingStats);
|
||||
RTC.addStreamListener(onStreamCreated,
|
||||
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||
xmpp.addListener(XMPPEvents.DISPOSE_CONFERENCE, onDisposeConference);
|
||||
}
|
||||
|
||||
};
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
/* jshint -W117 */
|
||||
var TraceablePeerConnection = require("./TraceablePeerConnection");
|
||||
var SDPDiffer = require("./SDPDiffer");
|
||||
var SDPUtil = require("./SDPUtil");
|
||||
var SDP = require("./SDP");
|
||||
|
||||
// Jingle stuff
|
||||
function JingleSession(me, sid, connection) {
|
||||
function JingleSession(me, sid, connection, service) {
|
||||
this.me = me;
|
||||
this.sid = sid;
|
||||
this.connection = connection;
|
||||
|
@ -12,13 +17,13 @@ function JingleSession(me, sid, connection) {
|
|||
this.localSDP = null;
|
||||
this.remoteSDP = null;
|
||||
this.relayedStreams = [];
|
||||
this.remoteStreams = [];
|
||||
this.startTime = null;
|
||||
this.stopTime = null;
|
||||
this.media_constraints = null;
|
||||
this.pc_constraints = null;
|
||||
this.ice_config = {};
|
||||
this.drip_container = [];
|
||||
this.service = service;
|
||||
|
||||
this.usetrickle = true;
|
||||
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);
|
||||
};
|
||||
this.peerconnection.onaddstream = function (event) {
|
||||
self.remoteStreams.push(event.stream);
|
||||
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) {
|
||||
// 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)
|
||||
$(document).trigger('remotestreamremoved.jingle', [event, self.sid]);
|
||||
};
|
||||
|
@ -99,7 +99,7 @@ JingleSession.prototype.initiate = function (peerjid, isInitiator) {
|
|||
this.stopTime = new Date();
|
||||
break;
|
||||
}
|
||||
$(document).trigger('iceconnectionstatechange.jingle', [self.sid, self]);
|
||||
onIceConnectionStateChange(self.sid, self);
|
||||
};
|
||||
// add any local and relayed 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 () {
|
||||
var self = this;
|
||||
this.state = 'active';
|
||||
|
@ -145,12 +188,13 @@ JingleSession.prototype.accept = function () {
|
|||
// FIXME: change any inactive to sendrecv or whatever they were originally
|
||||
sdp = sdp.replace('a=inactive', 'a=sendrecv');
|
||||
}
|
||||
var self = this;
|
||||
this.peerconnection.setLocalDescription(new RTCSessionDescription({type: 'answer', sdp: sdp}),
|
||||
function () {
|
||||
//console.log('setLocalDescription success');
|
||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||
self.setLocalDescription();
|
||||
|
||||
this.connection.sendIQ(accept,
|
||||
self.connection.sendIQ(accept,
|
||||
function () {
|
||||
var ack = {};
|
||||
ack.source = 'answer';
|
||||
|
@ -347,8 +391,8 @@ JingleSession.prototype.createdOffer = function (sdp) {
|
|||
action: 'session-initiate',
|
||||
initiator: this.initiator,
|
||||
sid: this.sid});
|
||||
this.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
|
||||
this.connection.sendIQ(init,
|
||||
self.localSDP.toJingle(init, this.initiator == this.me ? 'initiator' : 'responder', this.localStreamsSSRC);
|
||||
self.connection.sendIQ(init,
|
||||
function () {
|
||||
var ack = {};
|
||||
ack.source = 'offer';
|
||||
|
@ -369,13 +413,11 @@ JingleSession.prototype.createdOffer = function (sdp) {
|
|||
sdp.sdp = this.localSDP.raw;
|
||||
this.peerconnection.setLocalDescription(sdp,
|
||||
function () {
|
||||
if(this.usetrickle)
|
||||
if(self.usetrickle)
|
||||
{
|
||||
sendJingle();
|
||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||
}
|
||||
else
|
||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||
self.setLocalDescription();
|
||||
//console.log('setLocalDescription success');
|
||||
},
|
||||
function (e) {
|
||||
|
@ -587,7 +629,7 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
|
|||
var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
|
||||
var publicLocalSDP = new SDP(publicLocalDesc.sdp);
|
||||
publicLocalSDP.toJingle(accept, self.initiator == self.me ? 'initiator' : 'responder', ssrcs);
|
||||
this.connection.sendIQ(accept,
|
||||
self.connection.sendIQ(accept,
|
||||
function () {
|
||||
var ack = {};
|
||||
ack.source = 'answer';
|
||||
|
@ -610,10 +652,8 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
|
|||
//console.log('setLocalDescription success');
|
||||
if (self.usetrickle && !self.usepranswer) {
|
||||
sendJingle();
|
||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||
}
|
||||
else
|
||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||
self.setLocalDescription();
|
||||
},
|
||||
function (e) {
|
||||
console.error('setLocalDescription failed', e);
|
||||
|
@ -799,7 +839,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
|
|||
if (this.peerconnection.signalingState == 'closed') return;
|
||||
if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
|
||||
// There is nothing to do since scheduled job might have been executed by another succeeding call
|
||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||
this.setLocalDescription();
|
||||
if(successCallback){
|
||||
successCallback();
|
||||
}
|
||||
|
@ -889,7 +929,7 @@ JingleSession.prototype.modifySources = function (successCallback) {
|
|||
self.peerconnection.setLocalDescription(modifiedAnswer,
|
||||
function() {
|
||||
//console.log('modified setLocalDescription ok');
|
||||
$(document).trigger('setLocalDescription.jingle', [self.sid]);
|
||||
self.setLocalDescription();
|
||||
if(successCallback){
|
||||
successCallback();
|
||||
}
|
||||
|
@ -1064,12 +1104,20 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
|
|||
} else if (this.videoMuteByUser) {
|
||||
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())
|
||||
{
|
||||
// Even if no change occurs, the specified callback is to be executed.
|
||||
// The specified callback may, optionally, return a successCallback
|
||||
// which is to be executed as well.
|
||||
var successCallback = callback(mute);
|
||||
var successCallback = localCallback(mute);
|
||||
|
||||
if (successCallback) {
|
||||
successCallback();
|
||||
|
@ -1079,14 +1127,14 @@ JingleSession.prototype.setVideoMute = function (mute, callback, options) {
|
|||
|
||||
this.hardMuteVideo(mute);
|
||||
|
||||
this.modifySources(callback(mute));
|
||||
this.modifySources(localCallback(mute));
|
||||
}
|
||||
};
|
||||
|
||||
// SDP-based mute by going recvonly/sendrecv
|
||||
// FIXME: should probably black out the screen as well
|
||||
JingleSession.prototype.toggleVideoMute = function (callback) {
|
||||
setVideoMute(RTC.localVideo.isMuted(), callback);
|
||||
this.service.setVideoMute(RTC.localVideo.isMuted(), callback);
|
||||
};
|
||||
|
||||
JingleSession.prototype.hardMuteVideo = function (muted) {
|
||||
|
@ -1172,8 +1220,170 @@ JingleSession.onJingleError = function (session, error)
|
|||
|
||||
JingleSession.onJingleFatalError = function (session, error)
|
||||
{
|
||||
sessionTerminated = true;
|
||||
this.service.sessionTerminated = true;
|
||||
connection.emuc.doLeave();
|
||||
UI.messageHandler.showError( "Sorry",
|
||||
"Internal application error[setRemoteDescription]");
|
||||
}
|
||||
|
||||
JingleSession.prototype.setLocalDescription = function () {
|
||||
// put our ssrcs into presence so other clients can identify our stream
|
||||
var newssrcs = [];
|
||||
var media = simulcast.parseMedia(this.peerconnection.localDescription);
|
||||
media.forEach(function (media) {
|
||||
|
||||
if(Object.keys(media.sources).length > 0) {
|
||||
// TODO(gp) maybe exclude FID streams?
|
||||
Object.keys(media.sources).forEach(function (ssrc) {
|
||||
newssrcs.push({
|
||||
'ssrc': ssrc,
|
||||
'type': media.type,
|
||||
'direction': media.direction
|
||||
});
|
||||
});
|
||||
}
|
||||
else if(this.localStreamsSSRC && this.localStreamsSSRC[media.type])
|
||||
{
|
||||
newssrcs.push({
|
||||
'ssrc': this.localStreamsSSRC[media.type],
|
||||
'type': media.type,
|
||||
'direction': media.direction
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
console.log('new ssrcs', newssrcs);
|
||||
|
||||
// Have to clear presence map to get rid of removed streams
|
||||
this.connection.emuc.clearPresenceMedia();
|
||||
|
||||
if (newssrcs.length > 0) {
|
||||
for (var i = 1; i <= newssrcs.length; i ++) {
|
||||
// Change video type to screen
|
||||
if (newssrcs[i-1].type === 'video' && desktopsharing.isUsingScreenStream()) {
|
||||
newssrcs[i-1].type = 'screen';
|
||||
}
|
||||
this.connection.emuc.addMediaToPresence(i,
|
||||
newssrcs[i-1].type, newssrcs[i-1].ssrc, newssrcs[i-1].direction);
|
||||
}
|
||||
|
||||
this.connection.emuc.sendPresence();
|
||||
}
|
||||
}
|
||||
|
||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||
function sendKeyframe(pc) {
|
||||
console.log('sendkeyframe', pc.iceConnectionState);
|
||||
if (pc.iceConnectionState !== 'connected') return; // safe...
|
||||
pc.setRemoteDescription(
|
||||
pc.remoteDescription,
|
||||
function () {
|
||||
pc.createAnswer(
|
||||
function (modifiedAnswer) {
|
||||
pc.setLocalDescription(
|
||||
modifiedAnswer,
|
||||
function () {
|
||||
// noop
|
||||
},
|
||||
function (error) {
|
||||
console.log('triggerKeyframe setLocalDescription failed', error);
|
||||
UI.messageHandler.showError();
|
||||
}
|
||||
);
|
||||
},
|
||||
function (error) {
|
||||
console.log('triggerKeyframe createAnswer failed', error);
|
||||
UI.messageHandler.showError();
|
||||
}
|
||||
);
|
||||
},
|
||||
function (error) {
|
||||
console.log('triggerKeyframe setRemoteDescription failed', error);
|
||||
UI.messageHandler.showError();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
JingleSession.prototype.remoteStreamAdded = function (data) {
|
||||
var self = this;
|
||||
var thessrc;
|
||||
|
||||
// look up an associated JID for a stream id
|
||||
if (data.stream.id && data.stream.id.indexOf('mixedmslabel') === -1) {
|
||||
// look only at a=ssrc: and _not_ at a=ssrc-group: lines
|
||||
|
||||
var ssrclines
|
||||
= SDPUtil.find_lines(this.peerconnection.remoteDescription.sdp, 'a=ssrc:');
|
||||
ssrclines = ssrclines.filter(function (line) {
|
||||
// NOTE(gp) previously we filtered on the mslabel, but that property
|
||||
// is not always present.
|
||||
// return line.indexOf('mslabel:' + data.stream.label) !== -1;
|
||||
|
||||
return ((line.indexOf('msid:' + data.stream.id) !== -1));
|
||||
});
|
||||
if (ssrclines.length) {
|
||||
thessrc = ssrclines[0].substring(7).split(' ')[0];
|
||||
|
||||
// We signal our streams (through Jingle to the focus) before we set
|
||||
// our presence (through which peers associate remote streams to
|
||||
// jids). So, it might arrive that a remote stream is added but
|
||||
// ssrc2jid is not yet updated and thus data.peerjid cannot be
|
||||
// successfully set. Here we wait for up to a second for the
|
||||
// presence to arrive.
|
||||
|
||||
if (!ssrc2jid[thessrc]) {
|
||||
// TODO(gp) limit wait duration to 1 sec.
|
||||
setTimeout(function(d) {
|
||||
return function() {
|
||||
self.remoteStreamAdded(d);
|
||||
}
|
||||
}(data), 250);
|
||||
return;
|
||||
}
|
||||
|
||||
// ok to overwrite the one from focus? might save work in colibri.js
|
||||
console.log('associated jid', ssrc2jid[thessrc], data.peerjid);
|
||||
if (ssrc2jid[thessrc]) {
|
||||
data.peerjid = ssrc2jid[thessrc];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: this code should be removed when firefox implement multistream support
|
||||
if(RTC.getBrowserType() == RTCBrowserType.RTC_BROWSER_FIREFOX)
|
||||
{
|
||||
if((notReceivedSSRCs.length == 0) ||
|
||||
!ssrc2jid[notReceivedSSRCs[notReceivedSSRCs.length - 1]])
|
||||
{
|
||||
// TODO(gp) limit wait duration to 1 sec.
|
||||
setTimeout(function(d) {
|
||||
return function() {
|
||||
self.remoteStreamAdded(d);
|
||||
}
|
||||
}(data), 250);
|
||||
return;
|
||||
}
|
||||
|
||||
thessrc = notReceivedSSRCs.pop();
|
||||
if (ssrc2jid[thessrc]) {
|
||||
data.peerjid = ssrc2jid[thessrc];
|
||||
}
|
||||
}
|
||||
|
||||
RTC.createRemoteStream(data, this.sid, thessrc);
|
||||
|
||||
var isVideo = data.stream.getVideoTracks().length > 0;
|
||||
// an attempt to work around https://github.com/jitsi/jitmeet/issues/32
|
||||
if (isVideo &&
|
||||
data.peerjid && this.peerjid === data.peerjid &&
|
||||
data.stream.getVideoTracks().length === 0 &&
|
||||
RTC.localVideo.getTracks().length > 0) {
|
||||
window.setTimeout(function () {
|
||||
sendKeyframe(self.peerconnection);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JingleSession;
|
|
@ -1,4 +1,6 @@
|
|||
/* jshint -W117 */
|
||||
var SDPUtil = require("./SDPUtil");
|
||||
|
||||
// SDP STUFF
|
||||
function SDP(sdp) {
|
||||
this.media = sdp.split('\r\nm=');
|
||||
|
@ -71,169 +73,6 @@ SDP.prototype.containsSSRC = function(ssrc) {
|
|||
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
|
||||
SDP.prototype.mangle = function () {
|
||||
|
@ -776,352 +615,6 @@ SDP.prototype.jingle2media = function (content) {
|
|||
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
|
||||
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 = SDP;
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
function SDPDiffer(mySDP, otherSDP) {
|
||||
this.mySDP = mySDP;
|
||||
this.otherSDP = otherSDP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns map of MediaChannel that contains only media not contained in <tt>otherSdp</tt>. Mapped by channel idx.
|
||||
* @param otherSdp the other SDP to check ssrc with.
|
||||
*/
|
||||
SDPDiffer.prototype.getNewMedia = function() {
|
||||
|
||||
// this could be useful in Array.prototype.
|
||||
function arrayEquals(array) {
|
||||
// if the other array is a falsy value, return
|
||||
if (!array)
|
||||
return false;
|
||||
|
||||
// compare lengths - can save a lot of time
|
||||
if (this.length != array.length)
|
||||
return false;
|
||||
|
||||
for (var i = 0, l=this.length; i < l; i++) {
|
||||
// Check if we have nested arrays
|
||||
if (this[i] instanceof Array && array[i] instanceof Array) {
|
||||
// recurse into the nested arrays
|
||||
if (!this[i].equals(array[i]))
|
||||
return false;
|
||||
}
|
||||
else if (this[i] != array[i]) {
|
||||
// Warning - two different object instances will never be equal: {x:20} != {x:20}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
var myMedias = this.mySDP.getMediaSsrcMap();
|
||||
var othersMedias = this.otherSDP.getMediaSsrcMap();
|
||||
var newMedia = {};
|
||||
Object.keys(othersMedias).forEach(function(othersMediaIdx) {
|
||||
var myMedia = myMedias[othersMediaIdx];
|
||||
var othersMedia = othersMedias[othersMediaIdx];
|
||||
if(!myMedia && othersMedia) {
|
||||
// Add whole channel
|
||||
newMedia[othersMediaIdx] = othersMedia;
|
||||
return;
|
||||
}
|
||||
// Look for new ssrcs accross the channel
|
||||
Object.keys(othersMedia.ssrcs).forEach(function(ssrc) {
|
||||
if(Object.keys(myMedia.ssrcs).indexOf(ssrc) === -1) {
|
||||
// Allocate channel if we've found ssrc that doesn't exist in our channel
|
||||
if(!newMedia[othersMediaIdx]){
|
||||
newMedia[othersMediaIdx] = {
|
||||
mediaindex: othersMedia.mediaindex,
|
||||
mid: othersMedia.mid,
|
||||
ssrcs: {},
|
||||
ssrcGroups: []
|
||||
};
|
||||
}
|
||||
newMedia[othersMediaIdx].ssrcs[ssrc] = othersMedia.ssrcs[ssrc];
|
||||
}
|
||||
});
|
||||
|
||||
// Look for new ssrc groups across the channels
|
||||
othersMedia.ssrcGroups.forEach(function(otherSsrcGroup){
|
||||
|
||||
// try to match the other ssrc-group with an ssrc-group of ours
|
||||
var matched = false;
|
||||
for (var i = 0; i < myMedia.ssrcGroups.length; i++) {
|
||||
var mySsrcGroup = myMedia.ssrcGroups[i];
|
||||
if (otherSsrcGroup.semantics == mySsrcGroup.semantics
|
||||
&& arrayEquals.apply(otherSsrcGroup.ssrcs, [mySsrcGroup.ssrcs])) {
|
||||
|
||||
matched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
// Allocate channel if we've found an ssrc-group that doesn't
|
||||
// exist in our channel
|
||||
|
||||
if(!newMedia[othersMediaIdx]){
|
||||
newMedia[othersMediaIdx] = {
|
||||
mediaindex: othersMedia.mediaindex,
|
||||
mid: othersMedia.mid,
|
||||
ssrcs: {},
|
||||
ssrcGroups: []
|
||||
};
|
||||
}
|
||||
newMedia[othersMediaIdx].ssrcGroups.push(otherSsrcGroup);
|
||||
}
|
||||
});
|
||||
});
|
||||
return newMedia;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends SSRC update IQ.
|
||||
* @param sdpMediaSsrcs SSRCs map obtained from SDP.getNewMedia. Cntains SSRCs to add/remove.
|
||||
* @param sid session identifier that will be put into the IQ.
|
||||
* @param initiator initiator identifier.
|
||||
* @param toJid destination Jid
|
||||
* @param isAdd indicates if this is remove or add operation.
|
||||
*/
|
||||
SDPDiffer.prototype.toJingle = function(modify) {
|
||||
var sdpMediaSsrcs = this.getNewMedia();
|
||||
var self = this;
|
||||
|
||||
// FIXME: only announce video ssrcs since we mix audio and dont need
|
||||
// the audio ssrcs therefore
|
||||
var modified = false;
|
||||
Object.keys(sdpMediaSsrcs).forEach(function(mediaindex){
|
||||
modified = true;
|
||||
var media = sdpMediaSsrcs[mediaindex];
|
||||
modify.c('content', {name: media.mid});
|
||||
|
||||
modify.c('description', {xmlns:'urn:xmpp:jingle:apps:rtp:1', media: media.mid});
|
||||
// FIXME: not completly sure this operates on blocks and / or handles different ssrcs correctly
|
||||
// generate sources from lines
|
||||
Object.keys(media.ssrcs).forEach(function(ssrcNum) {
|
||||
var mediaSsrc = media.ssrcs[ssrcNum];
|
||||
modify.c('source', { xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0' });
|
||||
modify.attrs({ssrc: mediaSsrc.ssrc});
|
||||
// iterate over ssrc lines
|
||||
mediaSsrc.lines.forEach(function (line) {
|
||||
var idx = line.indexOf(' ');
|
||||
var kv = line.substr(idx + 1);
|
||||
modify.c('parameter');
|
||||
if (kv.indexOf(':') == -1) {
|
||||
modify.attrs({ name: kv });
|
||||
} else {
|
||||
modify.attrs({ name: kv.split(':', 2)[0] });
|
||||
modify.attrs({ value: kv.split(':', 2)[1] });
|
||||
}
|
||||
modify.up(); // end of parameter
|
||||
});
|
||||
modify.up(); // end of source
|
||||
});
|
||||
|
||||
// generate source groups from lines
|
||||
media.ssrcGroups.forEach(function(ssrcGroup) {
|
||||
if (ssrcGroup.ssrcs.length != 0) {
|
||||
|
||||
modify.c('ssrc-group', {
|
||||
semantics: ssrcGroup.semantics,
|
||||
xmlns: 'urn:xmpp:jingle:apps:rtp:ssma:0'
|
||||
});
|
||||
|
||||
ssrcGroup.ssrcs.forEach(function (ssrc) {
|
||||
modify.c('source', { ssrc: ssrc })
|
||||
.up(); // end of source
|
||||
});
|
||||
modify.up(); // end of ssrc-group
|
||||
}
|
||||
});
|
||||
|
||||
modify.up(); // end of description
|
||||
modify.up(); // end of content
|
||||
});
|
||||
|
||||
return modified;
|
||||
};
|
||||
|
||||
module.exports = SDPDiffer;
|
|
@ -0,0 +1,349 @@
|
|||
SDPUtil = {
|
||||
iceparams: function (mediadesc, sessiondesc) {
|
||||
var data = null;
|
||||
if (SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc) &&
|
||||
SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc)) {
|
||||
data = {
|
||||
ufrag: SDPUtil.parse_iceufrag(SDPUtil.find_line(mediadesc, 'a=ice-ufrag:', sessiondesc)),
|
||||
pwd: SDPUtil.parse_icepwd(SDPUtil.find_line(mediadesc, 'a=ice-pwd:', sessiondesc))
|
||||
};
|
||||
}
|
||||
return data;
|
||||
},
|
||||
parse_iceufrag: function (line) {
|
||||
return line.substring(12);
|
||||
},
|
||||
build_iceufrag: function (frag) {
|
||||
return 'a=ice-ufrag:' + frag;
|
||||
},
|
||||
parse_icepwd: function (line) {
|
||||
return line.substring(10);
|
||||
},
|
||||
build_icepwd: function (pwd) {
|
||||
return 'a=ice-pwd:' + pwd;
|
||||
},
|
||||
parse_mid: function (line) {
|
||||
return line.substring(6);
|
||||
},
|
||||
parse_mline: function (line) {
|
||||
var parts = line.substring(2).split(' '),
|
||||
data = {};
|
||||
data.media = parts.shift();
|
||||
data.port = parts.shift();
|
||||
data.proto = parts.shift();
|
||||
if (parts[parts.length - 1] === '') { // trailing whitespace
|
||||
parts.pop();
|
||||
}
|
||||
data.fmt = parts;
|
||||
return data;
|
||||
},
|
||||
build_mline: function (mline) {
|
||||
return 'm=' + mline.media + ' ' + mline.port + ' ' + mline.proto + ' ' + mline.fmt.join(' ');
|
||||
},
|
||||
parse_rtpmap: function (line) {
|
||||
var parts = line.substring(9).split(' '),
|
||||
data = {};
|
||||
data.id = parts.shift();
|
||||
parts = parts[0].split('/');
|
||||
data.name = parts.shift();
|
||||
data.clockrate = parts.shift();
|
||||
data.channels = parts.length ? parts.shift() : '1';
|
||||
return data;
|
||||
},
|
||||
/**
|
||||
* Parses SDP line "a=sctpmap:..." and extracts SCTP port from it.
|
||||
* @param line eg. "a=sctpmap:5000 webrtc-datachannel"
|
||||
* @returns [SCTP port number, protocol, streams]
|
||||
*/
|
||||
parse_sctpmap: function (line)
|
||||
{
|
||||
var parts = line.substring(10).split(' ');
|
||||
var sctpPort = parts[0];
|
||||
var protocol = parts[1];
|
||||
// Stream count is optional
|
||||
var streamCount = parts.length > 2 ? parts[2] : null;
|
||||
return [sctpPort, protocol, streamCount];// SCTP port
|
||||
},
|
||||
build_rtpmap: function (el) {
|
||||
var line = 'a=rtpmap:' + el.getAttribute('id') + ' ' + el.getAttribute('name') + '/' + el.getAttribute('clockrate');
|
||||
if (el.getAttribute('channels') && el.getAttribute('channels') != '1') {
|
||||
line += '/' + el.getAttribute('channels');
|
||||
}
|
||||
return line;
|
||||
},
|
||||
parse_crypto: function (line) {
|
||||
var parts = line.substring(9).split(' '),
|
||||
data = {};
|
||||
data.tag = parts.shift();
|
||||
data['crypto-suite'] = parts.shift();
|
||||
data['key-params'] = parts.shift();
|
||||
if (parts.length) {
|
||||
data['session-params'] = parts.join(' ');
|
||||
}
|
||||
return data;
|
||||
},
|
||||
parse_fingerprint: function (line) { // RFC 4572
|
||||
var parts = line.substring(14).split(' '),
|
||||
data = {};
|
||||
data.hash = parts.shift();
|
||||
data.fingerprint = parts.shift();
|
||||
// TODO assert that fingerprint satisfies 2UHEX *(":" 2UHEX) ?
|
||||
return data;
|
||||
},
|
||||
parse_fmtp: function (line) {
|
||||
var parts = line.split(' '),
|
||||
i, key, value,
|
||||
data = [];
|
||||
parts.shift();
|
||||
parts = parts.join(' ').split(';');
|
||||
for (i = 0; i < parts.length; i++) {
|
||||
key = parts[i].split('=')[0];
|
||||
while (key.length && key[0] == ' ') {
|
||||
key = key.substring(1);
|
||||
}
|
||||
value = parts[i].split('=')[1];
|
||||
if (key && value) {
|
||||
data.push({name: key, value: value});
|
||||
} else if (key) {
|
||||
// rfc 4733 (DTMF) style stuff
|
||||
data.push({name: '', value: key});
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
parse_icecandidate: function (line) {
|
||||
var candidate = {},
|
||||
elems = line.split(' ');
|
||||
candidate.foundation = elems[0].substring(12);
|
||||
candidate.component = elems[1];
|
||||
candidate.protocol = elems[2].toLowerCase();
|
||||
candidate.priority = elems[3];
|
||||
candidate.ip = elems[4];
|
||||
candidate.port = elems[5];
|
||||
// elems[6] => "typ"
|
||||
candidate.type = elems[7];
|
||||
candidate.generation = 0; // default value, may be overwritten below
|
||||
for (var i = 8; i < elems.length; i += 2) {
|
||||
switch (elems[i]) {
|
||||
case 'raddr':
|
||||
candidate['rel-addr'] = elems[i + 1];
|
||||
break;
|
||||
case 'rport':
|
||||
candidate['rel-port'] = elems[i + 1];
|
||||
break;
|
||||
case 'generation':
|
||||
candidate.generation = elems[i + 1];
|
||||
break;
|
||||
case 'tcptype':
|
||||
candidate.tcptype = elems[i + 1];
|
||||
break;
|
||||
default: // TODO
|
||||
console.log('parse_icecandidate not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||
}
|
||||
}
|
||||
candidate.network = '1';
|
||||
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
||||
return candidate;
|
||||
},
|
||||
build_icecandidate: function (cand) {
|
||||
var line = ['a=candidate:' + cand.foundation, cand.component, cand.protocol, cand.priority, cand.ip, cand.port, 'typ', cand.type].join(' ');
|
||||
line += ' ';
|
||||
switch (cand.type) {
|
||||
case 'srflx':
|
||||
case 'prflx':
|
||||
case 'relay':
|
||||
if (cand.hasOwnAttribute('rel-addr') && cand.hasOwnAttribute('rel-port')) {
|
||||
line += 'raddr';
|
||||
line += ' ';
|
||||
line += cand['rel-addr'];
|
||||
line += ' ';
|
||||
line += 'rport';
|
||||
line += ' ';
|
||||
line += cand['rel-port'];
|
||||
line += ' ';
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (cand.hasOwnAttribute('tcptype')) {
|
||||
line += 'tcptype';
|
||||
line += ' ';
|
||||
line += cand.tcptype;
|
||||
line += ' ';
|
||||
}
|
||||
line += 'generation';
|
||||
line += ' ';
|
||||
line += cand.hasOwnAttribute('generation') ? cand.generation : '0';
|
||||
return line;
|
||||
},
|
||||
parse_ssrc: function (desc) {
|
||||
// proprietary mapping of a=ssrc lines
|
||||
// TODO: see "Jingle RTP Source Description" by Juberti and P. Thatcher on google docs
|
||||
// and parse according to that
|
||||
var lines = desc.split('\r\n'),
|
||||
data = {};
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].substring(0, 7) == 'a=ssrc:') {
|
||||
var idx = lines[i].indexOf(' ');
|
||||
data[lines[i].substr(idx + 1).split(':', 2)[0]] = lines[i].substr(idx + 1).split(':', 2)[1];
|
||||
}
|
||||
}
|
||||
return data;
|
||||
},
|
||||
parse_rtcpfb: function (line) {
|
||||
var parts = line.substr(10).split(' ');
|
||||
var data = {};
|
||||
data.pt = parts.shift();
|
||||
data.type = parts.shift();
|
||||
data.params = parts;
|
||||
return data;
|
||||
},
|
||||
parse_extmap: function (line) {
|
||||
var parts = line.substr(9).split(' ');
|
||||
var data = {};
|
||||
data.value = parts.shift();
|
||||
if (data.value.indexOf('/') != -1) {
|
||||
data.direction = data.value.substr(data.value.indexOf('/') + 1);
|
||||
data.value = data.value.substr(0, data.value.indexOf('/'));
|
||||
} else {
|
||||
data.direction = 'both';
|
||||
}
|
||||
data.uri = parts.shift();
|
||||
data.params = parts;
|
||||
return data;
|
||||
},
|
||||
find_line: function (haystack, needle, sessionpart) {
|
||||
var lines = haystack.split('\r\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].substring(0, needle.length) == needle) {
|
||||
return lines[i];
|
||||
}
|
||||
}
|
||||
if (!sessionpart) {
|
||||
return false;
|
||||
}
|
||||
// search session part
|
||||
lines = sessionpart.split('\r\n');
|
||||
for (var j = 0; j < lines.length; j++) {
|
||||
if (lines[j].substring(0, needle.length) == needle) {
|
||||
return lines[j];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
find_lines: function (haystack, needle, sessionpart) {
|
||||
var lines = haystack.split('\r\n'),
|
||||
needles = [];
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
if (lines[i].substring(0, needle.length) == needle)
|
||||
needles.push(lines[i]);
|
||||
}
|
||||
if (needles.length || !sessionpart) {
|
||||
return needles;
|
||||
}
|
||||
// search session part
|
||||
lines = sessionpart.split('\r\n');
|
||||
for (var j = 0; j < lines.length; j++) {
|
||||
if (lines[j].substring(0, needle.length) == needle) {
|
||||
needles.push(lines[j]);
|
||||
}
|
||||
}
|
||||
return needles;
|
||||
},
|
||||
candidateToJingle: function (line) {
|
||||
// a=candidate:2979166662 1 udp 2113937151 192.168.2.100 57698 typ host generation 0
|
||||
// <candidate component=... foundation=... generation=... id=... ip=... network=... port=... priority=... protocol=... type=.../>
|
||||
if (line.indexOf('candidate:') === 0) {
|
||||
line = 'a=' + line;
|
||||
} else if (line.substring(0, 12) != 'a=candidate:') {
|
||||
console.log('parseCandidate called with a line that is not a candidate line');
|
||||
console.log(line);
|
||||
return null;
|
||||
}
|
||||
if (line.substring(line.length - 2) == '\r\n') // chomp it
|
||||
line = line.substring(0, line.length - 2);
|
||||
var candidate = {},
|
||||
elems = line.split(' '),
|
||||
i;
|
||||
if (elems[6] != 'typ') {
|
||||
console.log('did not find typ in the right place');
|
||||
console.log(line);
|
||||
return null;
|
||||
}
|
||||
candidate.foundation = elems[0].substring(12);
|
||||
candidate.component = elems[1];
|
||||
candidate.protocol = elems[2].toLowerCase();
|
||||
candidate.priority = elems[3];
|
||||
candidate.ip = elems[4];
|
||||
candidate.port = elems[5];
|
||||
// elems[6] => "typ"
|
||||
candidate.type = elems[7];
|
||||
|
||||
candidate.generation = '0'; // default, may be overwritten below
|
||||
for (i = 8; i < elems.length; i += 2) {
|
||||
switch (elems[i]) {
|
||||
case 'raddr':
|
||||
candidate['rel-addr'] = elems[i + 1];
|
||||
break;
|
||||
case 'rport':
|
||||
candidate['rel-port'] = elems[i + 1];
|
||||
break;
|
||||
case 'generation':
|
||||
candidate.generation = elems[i + 1];
|
||||
break;
|
||||
case 'tcptype':
|
||||
candidate.tcptype = elems[i + 1];
|
||||
break;
|
||||
default: // TODO
|
||||
console.log('not translating "' + elems[i] + '" = "' + elems[i + 1] + '"');
|
||||
}
|
||||
}
|
||||
candidate.network = '1';
|
||||
candidate.id = Math.random().toString(36).substr(2, 10); // not applicable to SDP -- FIXME: should be unique, not just random
|
||||
return candidate;
|
||||
},
|
||||
candidateFromJingle: function (cand) {
|
||||
var line = 'a=candidate:';
|
||||
line += cand.getAttribute('foundation');
|
||||
line += ' ';
|
||||
line += cand.getAttribute('component');
|
||||
line += ' ';
|
||||
line += cand.getAttribute('protocol'); //.toUpperCase(); // chrome M23 doesn't like this
|
||||
line += ' ';
|
||||
line += cand.getAttribute('priority');
|
||||
line += ' ';
|
||||
line += cand.getAttribute('ip');
|
||||
line += ' ';
|
||||
line += cand.getAttribute('port');
|
||||
line += ' ';
|
||||
line += 'typ';
|
||||
line += ' ' + cand.getAttribute('type');
|
||||
line += ' ';
|
||||
switch (cand.getAttribute('type')) {
|
||||
case 'srflx':
|
||||
case 'prflx':
|
||||
case 'relay':
|
||||
if (cand.getAttribute('rel-addr') && cand.getAttribute('rel-port')) {
|
||||
line += 'raddr';
|
||||
line += ' ';
|
||||
line += cand.getAttribute('rel-addr');
|
||||
line += ' ';
|
||||
line += 'rport';
|
||||
line += ' ';
|
||||
line += cand.getAttribute('rel-port');
|
||||
line += ' ';
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (cand.getAttribute('protocol').toLowerCase() == 'tcp') {
|
||||
line += 'tcptype';
|
||||
line += ' ';
|
||||
line += cand.getAttribute('tcptype');
|
||||
line += ' ';
|
||||
}
|
||||
line += 'generation';
|
||||
line += ' ';
|
||||
line += cand.getAttribute('generation') || '0';
|
||||
return line + '\r\n';
|
||||
}
|
||||
};
|
||||
module.exports = SDPUtil;
|
|
@ -262,3 +262,5 @@ TraceablePeerConnection.prototype.getStats = function(callback, errback) {
|
|||
}
|
||||
};
|
||||
|
||||
module.exports = TraceablePeerConnection;
|
||||
|
|
@ -1,47 +1,53 @@
|
|||
/* global $, $iq, config, connection, Etherpad, hangUp, messageHandler,
|
||||
/* global $, $iq, config, connection, UI, messageHandler,
|
||||
roomName, sessionTerminated, Strophe, Util */
|
||||
/**
|
||||
* Contains logic responsible for enabling/disabling functionality available
|
||||
* only to moderator users.
|
||||
*/
|
||||
var Moderator = (function (my) {
|
||||
var connection = null;
|
||||
var focusUserJid;
|
||||
var getNextTimeout = Util.createExpBackoffTimer(1000);
|
||||
var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
|
||||
// External authentication stuff
|
||||
var externalAuthEnabled = false;
|
||||
// Sip gateway can be enabled by configuring Jigasi host in config.js or
|
||||
// it will be enabled automatically if focus detects the component through
|
||||
// service discovery.
|
||||
var sipGatewayEnabled = config.hosts.call_control !== undefined;
|
||||
|
||||
var focusUserJid;
|
||||
var getNextTimeout = Util.createExpBackoffTimer(1000);
|
||||
var getNextErrorTimeout = Util.createExpBackoffTimer(1000);
|
||||
// External authentication stuff
|
||||
var externalAuthEnabled = false;
|
||||
// Sip gateway can be enabled by configuring Jigasi host in config.js or
|
||||
// it will be enabled automatically if focus detects the component through
|
||||
// service discovery.
|
||||
var sipGatewayEnabled = config.hosts.call_control !== undefined;
|
||||
|
||||
my.isModerator = function () {
|
||||
var Moderator = {
|
||||
isModerator: function () {
|
||||
return connection && connection.emuc.isModerator();
|
||||
};
|
||||
},
|
||||
|
||||
my.isPeerModerator = function (peerJid) {
|
||||
return connection && connection.emuc.getMemberRole(peerJid) === 'moderator';
|
||||
};
|
||||
isPeerModerator: function (peerJid) {
|
||||
return connection &&
|
||||
connection.emuc.getMemberRole(peerJid) === 'moderator';
|
||||
},
|
||||
|
||||
my.isExternalAuthEnabled = function () {
|
||||
isExternalAuthEnabled: function () {
|
||||
return externalAuthEnabled;
|
||||
};
|
||||
},
|
||||
|
||||
my.isSipGatewayEnabled = function () {
|
||||
isSipGatewayEnabled: function () {
|
||||
return sipGatewayEnabled;
|
||||
};
|
||||
},
|
||||
|
||||
my.init = function () {
|
||||
Moderator.onLocalRoleChange = function (from, member, pres) {
|
||||
setConnection: function (con) {
|
||||
connection = con;
|
||||
},
|
||||
|
||||
init: function (xmpp) {
|
||||
this.xmppService = xmpp;
|
||||
this.onLocalRoleChange = function (from, member, pres) {
|
||||
UI.onModeratorStatusChanged(Moderator.isModerator());
|
||||
};
|
||||
};
|
||||
},
|
||||
|
||||
my.onMucLeft = function (jid) {
|
||||
onMucLeft: function (jid) {
|
||||
console.info("Someone left is it focus ? " + jid);
|
||||
var resource = Strophe.getResourceFromJid(jid);
|
||||
if (resource === 'focus' && !sessionTerminated) {
|
||||
if (resource === 'focus' && !this.xmppService.sessionTerminated) {
|
||||
console.info(
|
||||
"Focus has left the room - leaving conference");
|
||||
//hangUp();
|
||||
|
@ -49,20 +55,20 @@ var Moderator = (function (my) {
|
|||
// FIXME: show some message before reload
|
||||
location.reload();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
my.setFocusUserJid = function (focusJid) {
|
||||
setFocusUserJid: function (focusJid) {
|
||||
if (!focusUserJid) {
|
||||
focusUserJid = focusJid;
|
||||
console.info("Focus jid set to: " + focusUserJid);
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
my.getFocusUserJid = function () {
|
||||
getFocusUserJid: function () {
|
||||
return focusUserJid;
|
||||
};
|
||||
},
|
||||
|
||||
my.getFocusComponent = function () {
|
||||
getFocusComponent: function () {
|
||||
// Get focus component address
|
||||
var focusComponent = config.hosts.focus;
|
||||
// If not specified use default: 'focus.domain'
|
||||
|
@ -70,70 +76,64 @@ var Moderator = (function (my) {
|
|||
focusComponent = 'focus.' + config.hosts.domain;
|
||||
}
|
||||
return focusComponent;
|
||||
};
|
||||
},
|
||||
|
||||
my.createConferenceIq = function () {
|
||||
createConferenceIq: function (roomName) {
|
||||
// Generate create conference IQ
|
||||
var elem = $iq({to: Moderator.getFocusComponent(), type: 'set'});
|
||||
elem.c('conference', {
|
||||
xmlns: 'http://jitsi.org/protocol/focus',
|
||||
room: roomName
|
||||
});
|
||||
if (config.hosts.bridge !== undefined)
|
||||
{
|
||||
if (config.hosts.bridge !== undefined) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'bridge', value: config.hosts.bridge})
|
||||
.up();
|
||||
}
|
||||
// Tell the focus we have Jigasi configured
|
||||
if (config.hosts.call_control !== undefined)
|
||||
{
|
||||
if (config.hosts.call_control !== undefined) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'call_control', value: config.hosts.call_control})
|
||||
.up();
|
||||
}
|
||||
if (config.channelLastN !== undefined)
|
||||
{
|
||||
if (config.channelLastN !== undefined) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'channelLastN', value: config.channelLastN})
|
||||
.up();
|
||||
}
|
||||
if (config.adaptiveLastN !== undefined)
|
||||
{
|
||||
if (config.adaptiveLastN !== undefined) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'adaptiveLastN', value: config.adaptiveLastN})
|
||||
.up();
|
||||
}
|
||||
if (config.adaptiveSimulcast !== undefined)
|
||||
{
|
||||
if (config.adaptiveSimulcast !== undefined) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'adaptiveSimulcast', value: config.adaptiveSimulcast})
|
||||
.up();
|
||||
}
|
||||
if (config.openSctp !== undefined)
|
||||
{
|
||||
if (config.openSctp !== undefined) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'openSctp', value: config.openSctp})
|
||||
.up();
|
||||
}
|
||||
if (config.enableFirefoxSupport !== undefined)
|
||||
{
|
||||
if (config.enableFirefoxSupport !== undefined) {
|
||||
elem.c(
|
||||
'property',
|
||||
{ name: 'enableFirefoxHacks', value: config.enableFirefoxSupport})
|
||||
{ name: 'enableFirefoxHacks',
|
||||
value: config.enableFirefoxSupport})
|
||||
.up();
|
||||
}
|
||||
elem.up();
|
||||
return elem;
|
||||
};
|
||||
},
|
||||
|
||||
my.parseConfigOptions = function (resultIq) {
|
||||
parseConfigOptions: function (resultIq) {
|
||||
|
||||
Moderator.setFocusUserJid(
|
||||
$(resultIq).find('conference').attr('focusjid'));
|
||||
|
@ -149,20 +149,20 @@ var Moderator = (function (my) {
|
|||
// Check if focus has auto-detected Jigasi component(this will be also
|
||||
// included if we have passed our host from the config)
|
||||
if ($(resultIq).find(
|
||||
'>conference>property[name=\'sipGatewayEnabled\']').length) {
|
||||
'>conference>property[name=\'sipGatewayEnabled\']').length) {
|
||||
sipGatewayEnabled = true;
|
||||
}
|
||||
|
||||
console.info("Sip gateway enabled: " + sipGatewayEnabled);
|
||||
};
|
||||
},
|
||||
|
||||
// FIXME: we need to show the fact that we're waiting for the focus
|
||||
// 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
|
||||
Moderator.setFocusUserJid(config.focusUserJid);
|
||||
// Send create conference IQ
|
||||
var iq = Moderator.createConferenceIq();
|
||||
var iq = Moderator.createConferenceIq(roomName);
|
||||
connection.sendIQ(
|
||||
iq,
|
||||
function (result) {
|
||||
|
@ -190,7 +190,9 @@ var Moderator = (function (my) {
|
|||
// Not authorized to create new room
|
||||
if ($(error).find('>error>not-authorized').length) {
|
||||
console.warn("Unauthorized to start the conference");
|
||||
UI.onAuthenticationRequired();
|
||||
UI.onAuthenticationRequired(function () {
|
||||
Moderator.allocateConferenceFocus(roomName, callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
var waitMs = getNextErrorTimeout();
|
||||
|
@ -198,8 +200,9 @@ var Moderator = (function (my) {
|
|||
// Show message
|
||||
UI.messageHandler.notify(
|
||||
'Conference focus', 'disconnected',
|
||||
Moderator.getFocusComponent() +
|
||||
' not available - retry in ' + (waitMs / 1000) + ' sec');
|
||||
Moderator.getFocusComponent() +
|
||||
' not available - retry in ' +
|
||||
(waitMs / 1000) + ' sec');
|
||||
// Reset response timeout
|
||||
getNextTimeout(true);
|
||||
window.setTimeout(
|
||||
|
@ -208,9 +211,9 @@ var Moderator = (function (my) {
|
|||
}, waitMs);
|
||||
}
|
||||
);
|
||||
};
|
||||
},
|
||||
|
||||
my.getAuthUrl = function (urlCallback) {
|
||||
getAuthUrl: function (roomName, urlCallback) {
|
||||
var iq = $iq({to: Moderator.getFocusComponent(), type: 'get'});
|
||||
iq.c('auth-url', {
|
||||
xmlns: 'http://jitsi.org/protocol/focus',
|
||||
|
@ -232,10 +235,10 @@ var Moderator = (function (my) {
|
|||
console.error("Get auth url error", error);
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return my;
|
||||
}(Moderator || {}));
|
||||
module.exports = Moderator;
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
|
||||
Toolbar, Util */
|
||||
var Moderator = require("./moderator");
|
||||
|
||||
|
||||
var recordingToken = null;
|
||||
var recordingEnabled;
|
||||
|
||||
/**
|
||||
* Whether to use a jirecon component for recording, or use the videobridge
|
||||
* through COLIBRI.
|
||||
*/
|
||||
var useJirecon = (typeof config.hosts.jirecon != "undefined");
|
||||
|
||||
/**
|
||||
* The ID of the jirecon recording session. Jirecon generates it when we
|
||||
* initially start recording, and it needs to be used in subsequent requests
|
||||
* to jirecon.
|
||||
*/
|
||||
var jireconRid = null;
|
||||
|
||||
function setRecordingToken(token) {
|
||||
recordingToken = token;
|
||||
}
|
||||
|
||||
function setRecording(state, token, callback) {
|
||||
if (useJirecon){
|
||||
this.setRecordingJirecon(state, token, callback);
|
||||
} else {
|
||||
this.setRecordingColibri(state, token, callback);
|
||||
}
|
||||
}
|
||||
|
||||
function setRecordingJirecon(state, token, callback) {
|
||||
if (state == recordingEnabled){
|
||||
return;
|
||||
}
|
||||
|
||||
var iq = $iq({to: config.hosts.jirecon, type: 'set'})
|
||||
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
|
||||
action: state ? 'start' : 'stop',
|
||||
mucjid: connection.emuc.roomjid});
|
||||
if (!state){
|
||||
iq.attrs({rid: jireconRid});
|
||||
}
|
||||
|
||||
console.log('Start recording');
|
||||
|
||||
connection.sendIQ(
|
||||
iq,
|
||||
function (result) {
|
||||
// TODO wait for an IQ with the real status, since this is
|
||||
// provisional?
|
||||
jireconRid = $(result).find('recording').attr('rid');
|
||||
console.log('Recording ' + (state ? 'started' : 'stopped') +
|
||||
'(jirecon)' + result);
|
||||
recordingEnabled = state;
|
||||
if (!state){
|
||||
jireconRid = null;
|
||||
}
|
||||
|
||||
callback(state);
|
||||
},
|
||||
function (error) {
|
||||
console.log('Failed to start recording, error: ', error);
|
||||
callback(recordingEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
// Sends a COLIBRI message which enables or disables (according to 'state')
|
||||
// the recording on the bridge. Waits for the result IQ and calls 'callback'
|
||||
// with the new recording state, according to the IQ.
|
||||
function setRecordingColibri(state, token, callback) {
|
||||
var elem = $iq({to: focusMucJid, type: 'set'});
|
||||
elem.c('conference', {
|
||||
xmlns: 'http://jitsi.org/protocol/colibri'
|
||||
});
|
||||
elem.c('recording', {state: state, token: token});
|
||||
|
||||
connection.sendIQ(elem,
|
||||
function (result) {
|
||||
console.log('Set recording "', state, '". Result:', result);
|
||||
var recordingElem = $(result).find('>conference>recording');
|
||||
var newState = ('true' === recordingElem.attr('state'));
|
||||
|
||||
recordingEnabled = newState;
|
||||
callback(newState);
|
||||
},
|
||||
function (error) {
|
||||
console.warn(error);
|
||||
callback(recordingEnabled);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var Recording = {
|
||||
toggleRecording: function (tokenEmptyCallback,
|
||||
startingCallback, startedCallback) {
|
||||
if (!Moderator.isModerator()) {
|
||||
console.log(
|
||||
'non-focus, or conference not yet organized:' +
|
||||
' not enabling recording');
|
||||
return;
|
||||
}
|
||||
|
||||
// Jirecon does not (currently) support a token.
|
||||
if (!recordingToken && !useJirecon) {
|
||||
tokenEmptyCallback(function (value) {
|
||||
setRecordingToken(value);
|
||||
this.toggleRecording();
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var oldState = recordingEnabled;
|
||||
startingCallback(!oldState);
|
||||
setRecording(!oldState,
|
||||
recordingToken,
|
||||
function (state) {
|
||||
console.log("New recording state: ", state);
|
||||
if (state === oldState) {
|
||||
// FIXME: new focus:
|
||||
// this will not work when moderator changes
|
||||
// during active session. Then it will assume that
|
||||
// recording status has changed to true, but it might have
|
||||
// been already true(and we only received actual status from
|
||||
// the focus).
|
||||
//
|
||||
// SO we start with status null, so that it is initialized
|
||||
// here and will fail only after second click, so if invalid
|
||||
// token was used we have to press the button twice before
|
||||
// current status will be fetched and token will be reset.
|
||||
//
|
||||
// Reliable way would be to return authentication error.
|
||||
// Or status update when moderator connects.
|
||||
// Or we have to stop recording session when current
|
||||
// moderator leaves the room.
|
||||
|
||||
// Failed to change, reset the token because it might
|
||||
// have been wrong
|
||||
setRecordingToken(null);
|
||||
}
|
||||
startedCallback(state);
|
||||
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Recording;
|
|
@ -0,0 +1,607 @@
|
|||
/* jshint -W117 */
|
||||
/* a simple MUC connection plugin
|
||||
* can only handle a single MUC room
|
||||
*/
|
||||
|
||||
var bridgeIsDown = false;
|
||||
|
||||
var Moderator = require("./moderator");
|
||||
|
||||
module.exports = function(XMPP, eventEmitter) {
|
||||
Strophe.addConnectionPlugin('emuc', {
|
||||
connection: null,
|
||||
roomjid: null,
|
||||
myroomjid: null,
|
||||
members: {},
|
||||
list_members: [], // so we can elect a new focus
|
||||
presMap: {},
|
||||
preziMap: {},
|
||||
joined: false,
|
||||
isOwner: false,
|
||||
role: null,
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
},
|
||||
initPresenceMap: function (myroomjid) {
|
||||
this.presMap['to'] = myroomjid;
|
||||
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
|
||||
},
|
||||
doJoin: function (jid, password) {
|
||||
this.myroomjid = jid;
|
||||
|
||||
console.info("Joined MUC as " + this.myroomjid);
|
||||
|
||||
this.initPresenceMap(this.myroomjid);
|
||||
|
||||
if (!this.roomjid) {
|
||||
this.roomjid = Strophe.getBareJidFromJid(jid);
|
||||
// add handlers (just once)
|
||||
this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
|
||||
this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
|
||||
this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
|
||||
this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
|
||||
}
|
||||
if (password !== undefined) {
|
||||
this.presMap['password'] = password;
|
||||
}
|
||||
this.sendPresence();
|
||||
},
|
||||
doLeave: function () {
|
||||
console.log("do leave", this.myroomjid);
|
||||
var pres = $pres({to: this.myroomjid, type: 'unavailable' });
|
||||
this.presMap.length = 0;
|
||||
this.connection.send(pres);
|
||||
},
|
||||
createNonAnonymousRoom: function () {
|
||||
// http://xmpp.org/extensions/xep-0045.html#createroom-reserved
|
||||
|
||||
var getForm = $iq({type: 'get', to: this.roomjid})
|
||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
|
||||
.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||
|
||||
this.connection.sendIQ(getForm, function (form) {
|
||||
|
||||
if (!$(form).find(
|
||||
'>query>x[xmlns="jabber:x:data"]' +
|
||||
'>field[var="muc#roomconfig_whois"]').length) {
|
||||
|
||||
console.error('non-anonymous rooms not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
var formSubmit = $iq({to: this.roomjid, type: 'set'})
|
||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
||||
|
||||
formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||
|
||||
formSubmit.c('field', {'var': 'FORM_TYPE'})
|
||||
.c('value')
|
||||
.t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
||||
|
||||
formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
|
||||
.c('value').t('anyone').up().up();
|
||||
|
||||
this.connection.sendIQ(formSubmit);
|
||||
|
||||
}, function (error) {
|
||||
console.error("Error getting room configuration form");
|
||||
});
|
||||
},
|
||||
onPresence: function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
|
||||
// What is this for? A workaround for something?
|
||||
if (pres.getAttribute('type')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse etherpad tag.
|
||||
var etherpad = $(pres).find('>etherpad');
|
||||
if (etherpad.length) {
|
||||
if (config.etherpad_base && !Moderator.isModerator()) {
|
||||
UI.initEtherpad(etherpad.text());
|
||||
}
|
||||
}
|
||||
|
||||
// Parse prezi tag.
|
||||
var presentation = $(pres).find('>prezi');
|
||||
if (presentation.length) {
|
||||
var url = presentation.attr('url');
|
||||
var current = presentation.find('>current').text();
|
||||
|
||||
console.log('presentation info received from', from, url);
|
||||
|
||||
if (this.preziMap[from] == null) {
|
||||
this.preziMap[from] = url;
|
||||
|
||||
$(document).trigger('presentationadded.muc', [from, url, current]);
|
||||
}
|
||||
else {
|
||||
$(document).trigger('gotoslide.muc', [from, url, current]);
|
||||
}
|
||||
}
|
||||
else if (this.preziMap[from] != null) {
|
||||
var url = this.preziMap[from];
|
||||
delete this.preziMap[from];
|
||||
$(document).trigger('presentationremoved.muc', [from, url]);
|
||||
}
|
||||
|
||||
// Parse audio info tag.
|
||||
var audioMuted = $(pres).find('>audiomuted');
|
||||
if (audioMuted.length) {
|
||||
$(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
|
||||
}
|
||||
|
||||
// Parse video info tag.
|
||||
var videoMuted = $(pres).find('>videomuted');
|
||||
if (videoMuted.length) {
|
||||
$(document).trigger('videomuted.muc', [from, videoMuted.text()]);
|
||||
}
|
||||
|
||||
var stats = $(pres).find('>stats');
|
||||
if (stats.length) {
|
||||
var statsObj = {};
|
||||
Strophe.forEachChild(stats[0], "stat", function (el) {
|
||||
statsObj[el.getAttribute("name")] = el.getAttribute("value");
|
||||
});
|
||||
connectionquality.updateRemoteStats(from, statsObj);
|
||||
}
|
||||
|
||||
// Parse status.
|
||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
|
||||
this.isOwner = true;
|
||||
this.createNonAnonymousRoom();
|
||||
}
|
||||
|
||||
// Parse roles.
|
||||
var member = {};
|
||||
member.show = $(pres).find('>show').text();
|
||||
member.status = $(pres).find('>status').text();
|
||||
var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
|
||||
member.affiliation = tmp.attr('affiliation');
|
||||
member.role = tmp.attr('role');
|
||||
|
||||
// Focus recognition
|
||||
member.jid = tmp.attr('jid');
|
||||
member.isFocus = false;
|
||||
if (member.jid
|
||||
&& member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
|
||||
member.isFocus = true;
|
||||
}
|
||||
|
||||
var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
|
||||
member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
|
||||
|
||||
if (from == this.myroomjid) {
|
||||
if (member.affiliation == 'owner') this.isOwner = true;
|
||||
if (this.role !== member.role) {
|
||||
this.role = member.role;
|
||||
if (Moderator.onLocalRoleChange)
|
||||
Moderator.onLocalRoleChange(from, member, pres);
|
||||
UI.onLocalRoleChange(from, member, pres);
|
||||
}
|
||||
if (!this.joined) {
|
||||
this.joined = true;
|
||||
eventEmitter.emit(XMPPEvents.MUC_JOINED, from, member);
|
||||
this.list_members.push(from);
|
||||
}
|
||||
} else if (this.members[from] === undefined) {
|
||||
// new participant
|
||||
this.members[from] = member;
|
||||
this.list_members.push(from);
|
||||
console.log('entered', from, member);
|
||||
if (member.isFocus) {
|
||||
focusMucJid = from;
|
||||
console.info("Ignore focus: " + from + ", real JID: " + member.jid);
|
||||
}
|
||||
else {
|
||||
var id = $(pres).find('>userID').text();
|
||||
var email = $(pres).find('>email');
|
||||
if (email.length > 0) {
|
||||
id = email.text();
|
||||
}
|
||||
UI.onMucEntered(from, id, member.displayName);
|
||||
API.triggerEvent("participantJoined", {jid: from});
|
||||
}
|
||||
} else {
|
||||
// Presence update for existing participant
|
||||
// Watch role change:
|
||||
if (this.members[from].role != member.role) {
|
||||
this.members[from].role = member.role;
|
||||
UI.onMucRoleChanged(member.role, member.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
// Always trigger presence to update bindings
|
||||
$(document).trigger('presence.muc', [from, member, pres]);
|
||||
this.parsePresence(from, member, pres);
|
||||
|
||||
// Trigger status message update
|
||||
if (member.status) {
|
||||
UI.onMucPresenceStatus(from, member);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
onPresenceUnavailable: function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
// Status code 110 indicates that this notification is "self-presence".
|
||||
if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
|
||||
delete this.members[from];
|
||||
this.list_members.splice(this.list_members.indexOf(from), 1);
|
||||
this.onParticipantLeft(from);
|
||||
}
|
||||
// If the status code is 110 this means we're leaving and we would like
|
||||
// to remove everyone else from our view, so we trigger the event.
|
||||
else if (this.list_members.length > 1) {
|
||||
for (var i = 0; i < this.list_members.length; i++) {
|
||||
var member = this.list_members[i];
|
||||
delete this.members[i];
|
||||
this.list_members.splice(i, 1);
|
||||
this.onParticipantLeft(member);
|
||||
}
|
||||
}
|
||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
|
||||
$(document).trigger('kicked.muc', [from]);
|
||||
if (this.myroomjid === from) {
|
||||
XMPP.disposeConference(false);
|
||||
eventEmitter.emit(XMPPEvents.KICKED);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onPresenceError: function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||
console.log('on password required', from);
|
||||
var self = this;
|
||||
UI.onPasswordReqiured(function (value) {
|
||||
self.doJoin(from, value);
|
||||
});
|
||||
} else if ($(pres).find(
|
||||
'>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||
var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
|
||||
if (toDomain === config.hosts.anonymousdomain) {
|
||||
// we are connected with anonymous domain and only non anonymous users can create rooms
|
||||
// we must authorize the user
|
||||
XMPP.promptLogin();
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
||||
pres);
|
||||
}
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
||||
pres);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
sendMessage: function (body, nickname) {
|
||||
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
||||
msg.c('body', body).up();
|
||||
if (nickname) {
|
||||
msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
|
||||
}
|
||||
this.connection.send(msg);
|
||||
API.triggerEvent("outgoingMessage", {"message": body});
|
||||
},
|
||||
setSubject: function (subject) {
|
||||
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
||||
msg.c('subject', subject);
|
||||
this.connection.send(msg);
|
||||
console.log("topic changed to " + subject);
|
||||
},
|
||||
onMessage: function (msg) {
|
||||
// FIXME: this is a hack. but jingle on muc makes nickchanges hard
|
||||
var from = msg.getAttribute('from');
|
||||
var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);
|
||||
|
||||
var txt = $(msg).find('>body').text();
|
||||
var type = msg.getAttribute("type");
|
||||
if (type == "error") {
|
||||
UI.chatAddError($(msg).find('>text').text(), txt);
|
||||
return true;
|
||||
}
|
||||
|
||||
var subject = $(msg).find('>subject');
|
||||
if (subject.length) {
|
||||
var subjectText = subject.text();
|
||||
if (subjectText || subjectText == "") {
|
||||
UI.chatSetSubject(subjectText);
|
||||
console.log("Subject is changed to " + subjectText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (txt) {
|
||||
console.log('chat', nick, txt);
|
||||
UI.updateChatConversation(from, nick, txt);
|
||||
if (from != this.myroomjid)
|
||||
API.triggerEvent("incomingMessage",
|
||||
{"from": from, "nick": nick, "message": txt});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
lockRoom: function (key, onSuccess, onError, onNotSupported) {
|
||||
//http://xmpp.org/extensions/xep-0045.html#roomconfig
|
||||
var ob = this;
|
||||
this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
|
||||
function (res) {
|
||||
if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
|
||||
var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
||||
formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||
formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
||||
formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
|
||||
// Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
|
||||
formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
|
||||
// FIXME: is muc#roomconfig_passwordprotectedroom required?
|
||||
this.connection.sendIQ(formsubmit,
|
||||
onSuccess,
|
||||
onError);
|
||||
} else {
|
||||
onNotSupported();
|
||||
}
|
||||
}, onError);
|
||||
},
|
||||
kick: function (jid) {
|
||||
var kickIQ = $iq({to: this.roomjid, type: 'set'})
|
||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
|
||||
.c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
|
||||
.c('reason').t('You have been kicked.').up().up().up();
|
||||
|
||||
this.connection.sendIQ(
|
||||
kickIQ,
|
||||
function (result) {
|
||||
console.log('Kick participant with jid: ', jid, result);
|
||||
},
|
||||
function (error) {
|
||||
console.log('Kick participant error: ', error);
|
||||
});
|
||||
},
|
||||
sendPresence: function () {
|
||||
var pres = $pres({to: this.presMap['to'] });
|
||||
pres.c('x', {xmlns: this.presMap['xns']});
|
||||
|
||||
if (this.presMap['password']) {
|
||||
pres.c('password').t(this.presMap['password']).up();
|
||||
}
|
||||
|
||||
pres.up();
|
||||
|
||||
// Send XEP-0115 'c' stanza that contains our capabilities info
|
||||
if (this.connection.caps) {
|
||||
this.connection.caps.node = config.clientNode;
|
||||
pres.c('c', this.connection.caps.generateCapsAttrs()).up();
|
||||
}
|
||||
|
||||
pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
|
||||
.t(navigator.userAgent).up();
|
||||
|
||||
if (this.presMap['bridgeIsDown']) {
|
||||
pres.c('bridgeIsDown').up();
|
||||
}
|
||||
|
||||
if (this.presMap['email']) {
|
||||
pres.c('email').t(this.presMap['email']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['userId']) {
|
||||
pres.c('userId').t(this.presMap['userId']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['displayName']) {
|
||||
// XEP-0172
|
||||
pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
|
||||
.t(this.presMap['displayName']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['audions']) {
|
||||
pres.c('audiomuted', {xmlns: this.presMap['audions']})
|
||||
.t(this.presMap['audiomuted']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['videons']) {
|
||||
pres.c('videomuted', {xmlns: this.presMap['videons']})
|
||||
.t(this.presMap['videomuted']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['statsns']) {
|
||||
var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
|
||||
for (var stat in this.presMap["stats"])
|
||||
if (this.presMap["stats"][stat] != null)
|
||||
stats.c("stat", {name: stat, value: this.presMap["stats"][stat]}).up();
|
||||
pres.up();
|
||||
}
|
||||
|
||||
if (this.presMap['prezins']) {
|
||||
pres.c('prezi',
|
||||
{xmlns: this.presMap['prezins'],
|
||||
'url': this.presMap['preziurl']})
|
||||
.c('current').t(this.presMap['prezicurrent']).up().up();
|
||||
}
|
||||
|
||||
if (this.presMap['etherpadns']) {
|
||||
pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
|
||||
.t(this.presMap['etherpadname']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['medians']) {
|
||||
pres.c('media', {xmlns: this.presMap['medians']});
|
||||
var sourceNumber = 0;
|
||||
Object.keys(this.presMap).forEach(function (key) {
|
||||
if (key.indexOf('source') >= 0) {
|
||||
sourceNumber++;
|
||||
}
|
||||
});
|
||||
if (sourceNumber > 0)
|
||||
for (var i = 1; i <= sourceNumber / 3; i++) {
|
||||
pres.c('source',
|
||||
{type: this.presMap['source' + i + '_type'],
|
||||
ssrc: this.presMap['source' + i + '_ssrc'],
|
||||
direction: this.presMap['source' + i + '_direction']
|
||||
|| 'sendrecv' }
|
||||
).up();
|
||||
}
|
||||
}
|
||||
|
||||
pres.up();
|
||||
// console.debug(pres.toString());
|
||||
this.connection.send(pres);
|
||||
},
|
||||
addDisplayNameToPresence: function (displayName) {
|
||||
this.presMap['displayName'] = displayName;
|
||||
},
|
||||
addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
|
||||
if (!this.presMap['medians'])
|
||||
this.presMap['medians'] = 'http://estos.de/ns/mjs';
|
||||
|
||||
this.presMap['source' + sourceNumber + '_type'] = mtype;
|
||||
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
|
||||
this.presMap['source' + sourceNumber + '_direction'] = direction;
|
||||
},
|
||||
clearPresenceMedia: function () {
|
||||
var self = this;
|
||||
Object.keys(this.presMap).forEach(function (key) {
|
||||
if (key.indexOf('source') != -1) {
|
||||
delete self.presMap[key];
|
||||
}
|
||||
});
|
||||
},
|
||||
addPreziToPresence: function (url, currentSlide) {
|
||||
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
|
||||
this.presMap['preziurl'] = url;
|
||||
this.presMap['prezicurrent'] = currentSlide;
|
||||
},
|
||||
removePreziFromPresence: function () {
|
||||
delete this.presMap['prezins'];
|
||||
delete this.presMap['preziurl'];
|
||||
delete this.presMap['prezicurrent'];
|
||||
},
|
||||
addCurrentSlideToPresence: function (currentSlide) {
|
||||
this.presMap['prezicurrent'] = currentSlide;
|
||||
},
|
||||
getPrezi: function (roomjid) {
|
||||
return this.preziMap[roomjid];
|
||||
},
|
||||
addEtherpadToPresence: function (etherpadName) {
|
||||
this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
|
||||
this.presMap['etherpadname'] = etherpadName;
|
||||
},
|
||||
addAudioInfoToPresence: function (isMuted) {
|
||||
this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
|
||||
this.presMap['audiomuted'] = isMuted.toString();
|
||||
},
|
||||
addVideoInfoToPresence: function (isMuted) {
|
||||
this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
|
||||
this.presMap['videomuted'] = isMuted.toString();
|
||||
},
|
||||
addConnectionInfoToPresence: function (stats) {
|
||||
this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
|
||||
this.presMap['stats'] = stats;
|
||||
},
|
||||
findJidFromResource: function (resourceJid) {
|
||||
if (resourceJid &&
|
||||
resourceJid === Strophe.getResourceFromJid(this.myroomjid)) {
|
||||
return this.myroomjid;
|
||||
}
|
||||
var peerJid = null;
|
||||
Object.keys(this.members).some(function (jid) {
|
||||
peerJid = jid;
|
||||
return Strophe.getResourceFromJid(jid) === resourceJid;
|
||||
});
|
||||
return peerJid;
|
||||
},
|
||||
addBridgeIsDownToPresence: function () {
|
||||
this.presMap['bridgeIsDown'] = true;
|
||||
},
|
||||
addEmailToPresence: function (email) {
|
||||
this.presMap['email'] = email;
|
||||
},
|
||||
addUserIdToPresence: function (userId) {
|
||||
this.presMap['userId'] = userId;
|
||||
},
|
||||
isModerator: function () {
|
||||
return this.role === 'moderator';
|
||||
},
|
||||
getMemberRole: function (peerJid) {
|
||||
if (this.members[peerJid]) {
|
||||
return this.members[peerJid].role;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onParticipantLeft: function (jid) {
|
||||
UI.onMucLeft(jid);
|
||||
|
||||
API.triggerEvent("participantLeft", {jid: jid});
|
||||
|
||||
delete jid2Ssrc[jid];
|
||||
|
||||
this.connection.jingle.terminateByJid(jid);
|
||||
|
||||
if (this.getPrezi(jid)) {
|
||||
$(document).trigger('presentationremoved.muc',
|
||||
[jid, this.getPrezi(jid)]);
|
||||
}
|
||||
|
||||
Moderator.onMucLeft(jid);
|
||||
},
|
||||
parsePresence: function (from, memeber, pres) {
|
||||
if($(pres).find(">bridgeIsDown").length > 0 && !bridgeIsDown) {
|
||||
bridgeIsDown = true;
|
||||
eventEmitter.emit(XMPPEvents.BRIDGE_DOWN);
|
||||
}
|
||||
|
||||
if(memeber.isFocus)
|
||||
return;
|
||||
|
||||
// Remove old ssrcs coming from the jid
|
||||
Object.keys(ssrc2jid).forEach(function (ssrc) {
|
||||
if (ssrc2jid[ssrc] == jid) {
|
||||
delete ssrc2jid[ssrc];
|
||||
delete ssrc2videoType[ssrc];
|
||||
}
|
||||
});
|
||||
|
||||
var changedStreams = [];
|
||||
$(pres).find('>media[xmlns="http://estos.de/ns/mjs"]>source').each(function (idx, ssrc) {
|
||||
//console.log(jid, 'assoc ssrc', ssrc.getAttribute('type'), ssrc.getAttribute('ssrc'));
|
||||
var ssrcV = ssrc.getAttribute('ssrc');
|
||||
ssrc2jid[ssrcV] = from;
|
||||
notReceivedSSRCs.push(ssrcV);
|
||||
|
||||
var type = ssrc.getAttribute('type');
|
||||
ssrc2videoType[ssrcV] = type;
|
||||
|
||||
var direction = ssrc.getAttribute('direction');
|
||||
|
||||
changedStreams.push({type: type, direction: direction});
|
||||
|
||||
});
|
||||
|
||||
eventEmitter.emit(XMPPEvents.CHANGED_STREAMS, from, changedStreams);
|
||||
|
||||
var displayName = !config.displayJids
|
||||
? memeber.displayName : Strophe.getResourceFromJid(from);
|
||||
|
||||
if (displayName && displayName.length > 0)
|
||||
{
|
||||
// $(document).trigger('displaynamechanged',
|
||||
// [jid, displayName]);
|
||||
eventEmitter.emit(XMPPEvents.DISPLAY_NAME_CHANGED, from, displayName);
|
||||
}
|
||||
|
||||
|
||||
var id = $(pres).find('>userID').text();
|
||||
var email = $(pres).find('>email');
|
||||
if(email.length > 0) {
|
||||
id = email.text();
|
||||
}
|
||||
|
||||
eventEmitter.emit(XMPPEvents.USER_ID_CHANGED, from, id);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,334 @@
|
|||
/* jshint -W117 */
|
||||
|
||||
var JingleSession = require("./JingleSession");
|
||||
|
||||
function CallIncomingJingle(sid, connection) {
|
||||
var sess = connection.jingle.sessions[sid];
|
||||
|
||||
// TODO: do we check activecall == null?
|
||||
activecall = sess;
|
||||
|
||||
statistics.onConferenceCreated(sess);
|
||||
RTC.onConferenceCreated(sess);
|
||||
|
||||
// TODO: check affiliation and/or role
|
||||
console.log('emuc data for', sess.peerjid, connection.emuc.members[sess.peerjid]);
|
||||
sess.usedrip = true; // not-so-naive trickle ice
|
||||
sess.sendAnswer();
|
||||
sess.accept();
|
||||
|
||||
};
|
||||
|
||||
module.exports = function(XMPP)
|
||||
{
|
||||
Strophe.addConnectionPlugin('jingle', {
|
||||
connection: null,
|
||||
sessions: {},
|
||||
jid2session: {},
|
||||
ice_config: {iceServers: []},
|
||||
pc_constraints: {},
|
||||
media_constraints: {
|
||||
mandatory: {
|
||||
'OfferToReceiveAudio': true,
|
||||
'OfferToReceiveVideo': true
|
||||
}
|
||||
// MozDontOfferDataChannel: true when this is firefox
|
||||
},
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
if (this.connection.disco) {
|
||||
// http://xmpp.org/extensions/xep-0167.html#support
|
||||
// http://xmpp.org/extensions/xep-0176.html#support
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:transports:ice-udp:1');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:audio');
|
||||
this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:video');
|
||||
|
||||
|
||||
// this is dealt with by SDP O/A so we don't need to annouce this
|
||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtcp-fb:0'); // XEP-0293
|
||||
//this.connection.disco.addFeature('urn:xmpp:jingle:apps:rtp:rtp-hdrext:0'); // XEP-0294
|
||||
if (config.useRtcpMux) {
|
||||
this.connection.disco.addFeature('urn:ietf:rfc:5761'); // rtcp-mux
|
||||
}
|
||||
if (config.useBundle) {
|
||||
this.connection.disco.addFeature('urn:ietf:rfc:5888'); // a=group, e.g. bundle
|
||||
}
|
||||
//this.connection.disco.addFeature('urn:ietf:rfc:5576'); // a=ssrc
|
||||
}
|
||||
this.connection.addHandler(this.onJingle.bind(this), 'urn:xmpp:jingle:1', 'iq', 'set', null, null);
|
||||
},
|
||||
onJingle: function (iq) {
|
||||
var sid = $(iq).find('jingle').attr('sid');
|
||||
var action = $(iq).find('jingle').attr('action');
|
||||
var fromJid = iq.getAttribute('from');
|
||||
// send ack first
|
||||
var ack = $iq({type: 'result',
|
||||
to: fromJid,
|
||||
id: iq.getAttribute('id')
|
||||
});
|
||||
console.log('on jingle ' + action + ' from ' + fromJid, iq);
|
||||
var sess = this.sessions[sid];
|
||||
if ('session-initiate' != action) {
|
||||
if (sess === null) {
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
// compare from to sess.peerjid (bare jid comparison for later compat with message-mode)
|
||||
// local jid is not checked
|
||||
if (Strophe.getBareJidFromJid(fromJid) != Strophe.getBareJidFromJid(sess.peerjid)) {
|
||||
console.warn('jid mismatch for session id', sid, fromJid, sess.peerjid);
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('item-not-found', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up()
|
||||
.c('unknown-session', {xmlns: 'urn:xmpp:jingle:errors:1'});
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
} else if (sess !== undefined) {
|
||||
// existing session with same session id
|
||||
// this might be out-of-order if the sess.peerjid is the same as from
|
||||
ack.type = 'error';
|
||||
ack.c('error', {type: 'cancel'})
|
||||
.c('service-unavailable', {xmlns: 'urn:ietf:params:xml:ns:xmpp-stanzas'}).up();
|
||||
console.warn('duplicate session id', sid);
|
||||
this.connection.send(ack);
|
||||
return true;
|
||||
}
|
||||
// FIXME: check for a defined action
|
||||
this.connection.send(ack);
|
||||
// see http://xmpp.org/extensions/xep-0166.html#concepts-session
|
||||
switch (action) {
|
||||
case 'session-initiate':
|
||||
sess = new JingleSession(
|
||||
$(iq).attr('to'), $(iq).find('jingle').attr('sid'),
|
||||
this.connection, XMPP);
|
||||
// configure session
|
||||
|
||||
sess.media_constraints = this.media_constraints;
|
||||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(fromJid, false);
|
||||
// FIXME: setRemoteDescription should only be done when this call is to be accepted
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'offer');
|
||||
|
||||
this.sessions[sess.sid] = sess;
|
||||
this.jid2session[sess.peerjid] = sess;
|
||||
|
||||
// the callback should either
|
||||
// .sendAnswer and .accept
|
||||
// or .sendTerminate -- not necessarily synchronus
|
||||
CallIncomingJingle(sess.sid, this.connection);
|
||||
break;
|
||||
case 'session-accept':
|
||||
sess.setRemoteDescription($(iq).find('>jingle'), 'answer');
|
||||
sess.accept();
|
||||
$(document).trigger('callaccepted.jingle', [sess.sid]);
|
||||
break;
|
||||
case 'session-terminate':
|
||||
// If this is not the focus sending the terminate, we have
|
||||
// nothing more to do here.
|
||||
if (Object.keys(this.sessions).length < 1
|
||||
|| !(this.sessions[Object.keys(this.sessions)[0]]
|
||||
instanceof JingleSession))
|
||||
{
|
||||
break;
|
||||
}
|
||||
console.log('terminating...', sess.sid);
|
||||
sess.terminate();
|
||||
this.terminate(sess.sid);
|
||||
if ($(iq).find('>jingle>reason').length) {
|
||||
$(document).trigger('callterminated.jingle', [
|
||||
sess.sid,
|
||||
sess.peerjid,
|
||||
$(iq).find('>jingle>reason>:first')[0].tagName,
|
||||
$(iq).find('>jingle>reason>text').text()
|
||||
]);
|
||||
} else {
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, sess.peerjid]);
|
||||
}
|
||||
break;
|
||||
case 'transport-info':
|
||||
sess.addIceCandidate($(iq).find('>jingle>content'));
|
||||
break;
|
||||
case 'session-info':
|
||||
var affected;
|
||||
if ($(iq).find('>jingle>ringing[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
$(document).trigger('ringing.jingle', [sess.sid]);
|
||||
} else if ($(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
affected = $(iq).find('>jingle>mute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||
$(document).trigger('mute.jingle', [sess.sid, affected]);
|
||||
} else if ($(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').length) {
|
||||
affected = $(iq).find('>jingle>unmute[xmlns="urn:xmpp:jingle:apps:rtp:info:1"]').attr('name');
|
||||
$(document).trigger('unmute.jingle', [sess.sid, affected]);
|
||||
}
|
||||
break;
|
||||
case 'addsource': // FIXME: proprietary, un-jingleish
|
||||
case 'source-add': // FIXME: proprietary
|
||||
sess.addSource($(iq).find('>jingle>content'), fromJid);
|
||||
break;
|
||||
case 'removesource': // FIXME: proprietary, un-jingleish
|
||||
case 'source-remove': // FIXME: proprietary
|
||||
sess.removeSource($(iq).find('>jingle>content'), fromJid);
|
||||
break;
|
||||
default:
|
||||
console.warn('jingle action not implemented', action);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
initiate: function (peerjid, myjid) { // initiate a new jinglesession to peerjid
|
||||
var sess = new JingleSession(myjid || this.connection.jid,
|
||||
Math.random().toString(36).substr(2, 12), // random string
|
||||
this.connection, XMPP);
|
||||
// configure session
|
||||
|
||||
sess.media_constraints = this.media_constraints;
|
||||
sess.pc_constraints = this.pc_constraints;
|
||||
sess.ice_config = this.ice_config;
|
||||
|
||||
sess.initiate(peerjid, true);
|
||||
this.sessions[sess.sid] = sess;
|
||||
this.jid2session[sess.peerjid] = sess;
|
||||
sess.sendOffer();
|
||||
return sess;
|
||||
},
|
||||
terminate: function (sid, reason, text) { // terminate by sessionid (or all sessions)
|
||||
if (sid === null || sid === undefined) {
|
||||
for (sid in this.sessions) {
|
||||
if (this.sessions[sid].state != 'ended') {
|
||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||
this.sessions[sid].terminate();
|
||||
}
|
||||
delete this.jid2session[this.sessions[sid].peerjid];
|
||||
delete this.sessions[sid];
|
||||
}
|
||||
} else if (this.sessions.hasOwnProperty(sid)) {
|
||||
if (this.sessions[sid].state != 'ended') {
|
||||
this.sessions[sid].sendTerminate(reason || (!this.sessions[sid].active()) ? 'cancel' : null, text);
|
||||
this.sessions[sid].terminate();
|
||||
}
|
||||
delete this.jid2session[this.sessions[sid].peerjid];
|
||||
delete this.sessions[sid];
|
||||
}
|
||||
},
|
||||
// Used to terminate a session when an unavailable presence is received.
|
||||
terminateByJid: function (jid) {
|
||||
if (this.jid2session.hasOwnProperty(jid)) {
|
||||
var sess = this.jid2session[jid];
|
||||
if (sess) {
|
||||
sess.terminate();
|
||||
console.log('peer went away silently', jid);
|
||||
delete this.sessions[sess.sid];
|
||||
delete this.jid2session[jid];
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, jid], 'gone');
|
||||
}
|
||||
}
|
||||
},
|
||||
terminateRemoteByJid: function (jid, reason) {
|
||||
if (this.jid2session.hasOwnProperty(jid)) {
|
||||
var sess = this.jid2session[jid];
|
||||
if (sess) {
|
||||
sess.sendTerminate(reason || (!sess.active()) ? 'kick' : null);
|
||||
sess.terminate();
|
||||
console.log('terminate peer with jid', sess.sid, jid);
|
||||
delete this.sessions[sess.sid];
|
||||
delete this.jid2session[jid];
|
||||
$(document).trigger('callterminated.jingle',
|
||||
[sess.sid, jid, 'kicked']);
|
||||
}
|
||||
}
|
||||
},
|
||||
getStunAndTurnCredentials: function () {
|
||||
// get stun and turn configuration from server via xep-0215
|
||||
// uses time-limited credentials as described in
|
||||
// http://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
|
||||
//
|
||||
// see https://code.google.com/p/prosody-modules/source/browse/mod_turncredentials/mod_turncredentials.lua
|
||||
// for a prosody module which implements this
|
||||
//
|
||||
// currently, this doesn't work with updateIce and therefore credentials with a long
|
||||
// validity have to be fetched before creating the peerconnection
|
||||
// TODO: implement refresh via updateIce as described in
|
||||
// https://code.google.com/p/webrtc/issues/detail?id=1650
|
||||
var self = this;
|
||||
this.connection.sendIQ(
|
||||
$iq({type: 'get', to: this.connection.domain})
|
||||
.c('services', {xmlns: 'urn:xmpp:extdisco:1'}).c('service', {host: 'turn.' + this.connection.domain}),
|
||||
function (res) {
|
||||
var iceservers = [];
|
||||
$(res).find('>services>service').each(function (idx, el) {
|
||||
el = $(el);
|
||||
var dict = {};
|
||||
var type = el.attr('type');
|
||||
switch (type) {
|
||||
case 'stun':
|
||||
dict.url = 'stun:' + el.attr('host');
|
||||
if (el.attr('port')) {
|
||||
dict.url += ':' + el.attr('port');
|
||||
}
|
||||
iceservers.push(dict);
|
||||
break;
|
||||
case 'turn':
|
||||
case 'turns':
|
||||
dict.url = type + ':';
|
||||
if (el.attr('username')) { // https://code.google.com/p/webrtc/issues/detail?id=1508
|
||||
if (navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./) && parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2], 10) < 28) {
|
||||
dict.url += el.attr('username') + '@';
|
||||
} else {
|
||||
dict.username = el.attr('username'); // only works in M28
|
||||
}
|
||||
}
|
||||
dict.url += el.attr('host');
|
||||
if (el.attr('port') && el.attr('port') != '3478') {
|
||||
dict.url += ':' + el.attr('port');
|
||||
}
|
||||
if (el.attr('transport') && el.attr('transport') != 'udp') {
|
||||
dict.url += '?transport=' + el.attr('transport');
|
||||
}
|
||||
if (el.attr('password')) {
|
||||
dict.credential = el.attr('password');
|
||||
}
|
||||
iceservers.push(dict);
|
||||
break;
|
||||
}
|
||||
});
|
||||
self.ice_config.iceServers = iceservers;
|
||||
},
|
||||
function (err) {
|
||||
console.warn('getting turn credentials failed', err);
|
||||
console.warn('is mod_turncredentials or similar installed?');
|
||||
}
|
||||
);
|
||||
// implement push?
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates the log data
|
||||
*/
|
||||
populateData: function () {
|
||||
var data = {};
|
||||
Object.keys(this.sessions).forEach(function (sid) {
|
||||
var session = this.sessions[sid];
|
||||
if (session.peerconnection && session.peerconnection.updateLog) {
|
||||
// FIXME: should probably be a .dump call
|
||||
data["jingle_" + session.sid] = {
|
||||
updateLog: session.peerconnection.updateLog,
|
||||
stats: session.peerconnection.stats,
|
||||
url: window.location.href
|
||||
};
|
||||
}
|
||||
});
|
||||
return data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/* global Strophe */
|
||||
module.exports = function () {
|
||||
|
||||
Strophe.addConnectionPlugin('logger', {
|
||||
// logs raw stanzas and makes them available for download as JSON
|
||||
connection: null,
|
||||
log: [],
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
this.connection.rawInput = this.log_incoming.bind(this);
|
||||
this.connection.rawOutput = this.log_outgoing.bind(this);
|
||||
},
|
||||
log_incoming: function (stanza) {
|
||||
this.log.push([new Date().getTime(), 'incoming', stanza]);
|
||||
},
|
||||
log_outgoing: function (stanza) {
|
||||
this.log.push([new Date().getTime(), 'outgoing', stanza]);
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,58 @@
|
|||
/* global $, $iq, config, connection, focusMucJid, forceMuted,
|
||||
setAudioMuted, Strophe */
|
||||
/**
|
||||
* Moderate connection plugin.
|
||||
*/
|
||||
module.exports = function (XMPP) {
|
||||
Strophe.addConnectionPlugin('moderate', {
|
||||
connection: null,
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
|
||||
this.connection.addHandler(this.onMute.bind(this),
|
||||
'http://jitsi.org/jitmeet/audio',
|
||||
'iq',
|
||||
'set',
|
||||
null,
|
||||
null);
|
||||
},
|
||||
setMute: function (jid, mute) {
|
||||
console.info("set mute", mute);
|
||||
var iqToFocus = $iq({to: focusMucJid, type: 'set'})
|
||||
.c('mute', {
|
||||
xmlns: 'http://jitsi.org/jitmeet/audio',
|
||||
jid: jid
|
||||
})
|
||||
.t(mute.toString())
|
||||
.up();
|
||||
|
||||
this.connection.sendIQ(
|
||||
iqToFocus,
|
||||
function (result) {
|
||||
console.log('set mute', result);
|
||||
},
|
||||
function (error) {
|
||||
console.log('set mute error', error);
|
||||
});
|
||||
},
|
||||
onMute: function (iq) {
|
||||
var from = iq.getAttribute('from');
|
||||
if (from !== focusMucJid) {
|
||||
console.warn("Ignored mute from non focus peer");
|
||||
return false;
|
||||
}
|
||||
var mute = $(iq).find('mute');
|
||||
if (mute.length) {
|
||||
var doMuteAudio = mute.text() === "true";
|
||||
UI.setAudioMuted(doMuteAudio);
|
||||
XMPP.forceMuted = doMuteAudio;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
eject: function (jid) {
|
||||
// We're not the focus, so can't terminate
|
||||
//connection.jingle.terminateRemoteByJid(jid, 'kick');
|
||||
this.connection.emuc.kick(jid);
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/* jshint -W117 */
|
||||
module.exports = function() {
|
||||
Strophe.addConnectionPlugin('rayo',
|
||||
{
|
||||
RAYO_XMLNS: 'urn:xmpp:rayo:1',
|
||||
connection: null,
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
if (this.connection.disco) {
|
||||
this.connection.disco.addFeature('urn:xmpp:rayo:client:1');
|
||||
}
|
||||
|
||||
this.connection.addHandler(
|
||||
this.onRayo.bind(this), this.RAYO_XMLNS, 'iq', 'set', null, null);
|
||||
},
|
||||
onRayo: function (iq) {
|
||||
console.info("Rayo IQ", iq);
|
||||
},
|
||||
dial: function (to, from, roomName, roomPass) {
|
||||
var self = this;
|
||||
var req = $iq(
|
||||
{
|
||||
type: 'set',
|
||||
to: focusMucJid
|
||||
}
|
||||
);
|
||||
req.c('dial',
|
||||
{
|
||||
xmlns: this.RAYO_XMLNS,
|
||||
to: to,
|
||||
from: from
|
||||
});
|
||||
req.c('header',
|
||||
{
|
||||
name: 'JvbRoomName',
|
||||
value: roomName
|
||||
}).up();
|
||||
|
||||
if (roomPass !== null && roomPass.length) {
|
||||
|
||||
req.c('header',
|
||||
{
|
||||
name: 'JvbRoomPassword',
|
||||
value: roomPass
|
||||
}).up();
|
||||
}
|
||||
|
||||
this.connection.sendIQ(
|
||||
req,
|
||||
function (result) {
|
||||
console.info('Dial result ', result);
|
||||
|
||||
var resource = $(result).find('ref').attr('uri');
|
||||
this.call_resource = resource.substr('xmpp:'.length);
|
||||
console.info(
|
||||
"Received call resource: " + this.call_resource);
|
||||
},
|
||||
function (error) {
|
||||
console.info('Dial error ', error);
|
||||
}
|
||||
);
|
||||
},
|
||||
hang_up: function () {
|
||||
if (!this.call_resource) {
|
||||
console.warn("No call in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var req = $iq(
|
||||
{
|
||||
type: 'set',
|
||||
to: this.call_resource
|
||||
}
|
||||
);
|
||||
req.c('hangup',
|
||||
{
|
||||
xmlns: this.RAYO_XMLNS
|
||||
});
|
||||
|
||||
this.connection.sendIQ(
|
||||
req,
|
||||
function (result) {
|
||||
console.info('Hangup result ', result);
|
||||
self.call_resource = null;
|
||||
},
|
||||
function (error) {
|
||||
console.info('Hangup error ', error);
|
||||
self.call_resource = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Strophe logger implementation. Logs from level WARN and above.
|
||||
*/
|
||||
module.exports = function () {
|
||||
|
||||
Strophe.log = function (level, msg) {
|
||||
switch (level) {
|
||||
case Strophe.LogLevel.WARN:
|
||||
console.warn("Strophe: " + msg);
|
||||
break;
|
||||
case Strophe.LogLevel.ERROR:
|
||||
case Strophe.LogLevel.FATAL:
|
||||
console.error("Strophe: " + msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
Strophe.getStatusString = function (status) {
|
||||
switch (status) {
|
||||
case Strophe.Status.ERROR:
|
||||
return "ERROR";
|
||||
case Strophe.Status.CONNECTING:
|
||||
return "CONNECTING";
|
||||
case Strophe.Status.CONNFAIL:
|
||||
return "CONNFAIL";
|
||||
case Strophe.Status.AUTHENTICATING:
|
||||
return "AUTHENTICATING";
|
||||
case Strophe.Status.AUTHFAIL:
|
||||
return "AUTHFAIL";
|
||||
case Strophe.Status.CONNECTED:
|
||||
return "CONNECTED";
|
||||
case Strophe.Status.DISCONNECTED:
|
||||
return "DISCONNECTED";
|
||||
case Strophe.Status.DISCONNECTING:
|
||||
return "DISCONNECTING";
|
||||
case Strophe.Status.ATTACHED:
|
||||
return "ATTACHED";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,422 @@
|
|||
var Moderator = require("./moderator");
|
||||
var EventEmitter = require("events");
|
||||
var Recording = require("./recording");
|
||||
var SDP = require("./SDP");
|
||||
|
||||
var eventEmitter = new EventEmitter();
|
||||
var connection = null;
|
||||
var authenticatedUser = false;
|
||||
var activecall = null;
|
||||
|
||||
function connect(jid, password, uiCredentials) {
|
||||
var bosh
|
||||
= uiCredentials.bosh || config.bosh || '/http-bind';
|
||||
connection = new Strophe.Connection(bosh);
|
||||
Moderator.setConnection(connection);
|
||||
|
||||
var settings = UI.getSettings();
|
||||
var email = settings.email;
|
||||
var displayName = settings.displayName;
|
||||
if(email) {
|
||||
connection.emuc.addEmailToPresence(email);
|
||||
} else {
|
||||
connection.emuc.addUserIdToPresence(settings.uid);
|
||||
}
|
||||
if(displayName) {
|
||||
connection.emuc.addDisplayNameToPresence(displayName);
|
||||
}
|
||||
|
||||
if (connection.disco) {
|
||||
// for chrome, add multistream cap
|
||||
}
|
||||
connection.jingle.pc_constraints = RTC.getPCConstraints();
|
||||
if (config.useIPv6) {
|
||||
// https://code.google.com/p/webrtc/issues/detail?id=2828
|
||||
if (!connection.jingle.pc_constraints.optional)
|
||||
connection.jingle.pc_constraints.optional = [];
|
||||
connection.jingle.pc_constraints.optional.push({googIPv6: true});
|
||||
}
|
||||
|
||||
if(!password)
|
||||
password = uiCredentials.password;
|
||||
|
||||
var anonymousConnectionFailed = false;
|
||||
connection.connect(jid, password, function (status, msg) {
|
||||
console.log('Strophe status changed to',
|
||||
Strophe.getStatusString(status));
|
||||
if (status === Strophe.Status.CONNECTED) {
|
||||
if (config.useStunTurn) {
|
||||
connection.jingle.getStunAndTurnCredentials();
|
||||
}
|
||||
UI.disableConnect();
|
||||
|
||||
console.info("My Jabber ID: " + connection.jid);
|
||||
|
||||
if(password)
|
||||
authenticatedUser = true;
|
||||
maybeDoJoin();
|
||||
} else if (status === Strophe.Status.CONNFAIL) {
|
||||
if(msg === 'x-strophe-bad-non-anon-jid') {
|
||||
anonymousConnectionFailed = true;
|
||||
}
|
||||
} else if (status === Strophe.Status.DISCONNECTED) {
|
||||
if(anonymousConnectionFailed) {
|
||||
// prompt user for username and password
|
||||
XMPP.promptLogin();
|
||||
}
|
||||
} else if (status === Strophe.Status.AUTHFAIL) {
|
||||
// wrong password or username, prompt user
|
||||
XMPP.promptLogin();
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function maybeDoJoin() {
|
||||
if (connection && connection.connected &&
|
||||
Strophe.getResourceFromJid(connection.jid)
|
||||
&& (RTC.localAudio || RTC.localVideo)) {
|
||||
// .connected is true while connecting?
|
||||
doJoin();
|
||||
}
|
||||
}
|
||||
|
||||
function doJoin() {
|
||||
var roomName = UI.generateRoomName();
|
||||
|
||||
Moderator.allocateConferenceFocus(
|
||||
roomName, UI.checkForNicknameAndJoin);
|
||||
}
|
||||
|
||||
function initStrophePlugins()
|
||||
{
|
||||
require("./strophe.emuc")(XMPP, eventEmitter);
|
||||
require("./strophe.jingle")();
|
||||
require("./strophe.moderate")(XMPP);
|
||||
require("./strophe.util")();
|
||||
require("./strophe.rayo")();
|
||||
require("./strophe.logger")();
|
||||
}
|
||||
|
||||
function registerListeners() {
|
||||
RTC.addStreamListener(maybeDoJoin,
|
||||
StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
|
||||
}
|
||||
|
||||
function setupEvents() {
|
||||
$(window).bind('beforeunload', function () {
|
||||
if (connection && connection.connected) {
|
||||
// ensure signout
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: config.bosh,
|
||||
async: false,
|
||||
cache: false,
|
||||
contentType: 'application/xml',
|
||||
data: "<body rid='" + (connection.rid || connection._proto.rid)
|
||||
+ "' xmlns='http://jabber.org/protocol/httpbind' sid='"
|
||||
+ (connection.sid || connection._proto.sid)
|
||||
+ "' type='terminate'>" +
|
||||
"<presence xmlns='jabber:client' type='unavailable'/>" +
|
||||
"</body>",
|
||||
success: function (data) {
|
||||
console.log('signed out');
|
||||
console.log(data);
|
||||
},
|
||||
error: function (XMLHttpRequest, textStatus, errorThrown) {
|
||||
console.log('signout error',
|
||||
textStatus + ' (' + errorThrown + ')');
|
||||
}
|
||||
});
|
||||
}
|
||||
XMPP.disposeConference(true);
|
||||
});
|
||||
}
|
||||
|
||||
var XMPP = {
|
||||
sessionTerminated: false,
|
||||
/**
|
||||
* Remembers if we were muted by the focus.
|
||||
* @type {boolean}
|
||||
*/
|
||||
forceMuted: false,
|
||||
start: function (uiCredentials) {
|
||||
setupEvents();
|
||||
initStrophePlugins();
|
||||
registerListeners();
|
||||
Moderator.init();
|
||||
var jid = uiCredentials.jid ||
|
||||
config.hosts.anonymousdomain ||
|
||||
config.hosts.domain ||
|
||||
window.location.hostname;
|
||||
connect(jid, null, uiCredentials);
|
||||
},
|
||||
promptLogin: function () {
|
||||
UI.showLoginPopup(connect);
|
||||
},
|
||||
joinRooom: function(roomName, useNicks, nick)
|
||||
{
|
||||
var roomjid;
|
||||
roomjid = roomName;
|
||||
|
||||
if (useNicks) {
|
||||
if (nick) {
|
||||
roomjid += '/' + nick;
|
||||
} else {
|
||||
roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
|
||||
}
|
||||
} else {
|
||||
|
||||
var tmpJid = Strophe.getNodeFromJid(connection.jid);
|
||||
|
||||
if(!authenticatedUser)
|
||||
tmpJid = tmpJid.substr(0, 8);
|
||||
|
||||
roomjid += '/' + tmpJid;
|
||||
}
|
||||
connection.emuc.doJoin(roomjid);
|
||||
},
|
||||
myJid: function () {
|
||||
if(!connection)
|
||||
return null;
|
||||
return connection.emuc.myroomjid;
|
||||
},
|
||||
myResource: function () {
|
||||
if(!connection || ! connection.emuc.myroomjid)
|
||||
return null;
|
||||
return Strophe.getResourceFromJid(connection.emuc.myroomjid);
|
||||
},
|
||||
disposeConference: function (onUnload) {
|
||||
eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
|
||||
var handler = activecall;
|
||||
if (handler && handler.peerconnection) {
|
||||
// FIXME: probably removing streams is not required and close() should
|
||||
// be enough
|
||||
if (RTC.localAudio) {
|
||||
handler.peerconnection.removeStream(RTC.localAudio.getOriginalStream(), onUnload);
|
||||
}
|
||||
if (RTC.localVideo) {
|
||||
handler.peerconnection.removeStream(RTC.localVideo.getOriginalStream(), onUnload);
|
||||
}
|
||||
handler.peerconnection.close();
|
||||
}
|
||||
activecall = null;
|
||||
if(!onUnload)
|
||||
{
|
||||
this.sessionTerminated = true;
|
||||
connection.emuc.doLeave();
|
||||
}
|
||||
},
|
||||
addListener: function(type, listener)
|
||||
{
|
||||
eventEmitter.on(type, listener);
|
||||
},
|
||||
removeListener: function (type, listener) {
|
||||
eventEmitter.removeListener(type, listener);
|
||||
},
|
||||
allocateConferenceFocus: function(roomName, callback) {
|
||||
Moderator.allocateConferenceFocus(roomName, callback);
|
||||
},
|
||||
isModerator: function () {
|
||||
return Moderator.isModerator();
|
||||
},
|
||||
isSipGatewayEnabled: function () {
|
||||
return Moderator.isSipGatewayEnabled();
|
||||
},
|
||||
isExternalAuthEnabled: function () {
|
||||
return Moderator.isExternalAuthEnabled();
|
||||
},
|
||||
switchStreams: function (stream, oldStream, callback) {
|
||||
if (activecall) {
|
||||
// FIXME: will block switchInProgress on true value in case of exception
|
||||
activecall.switchStreams(stream, oldStream, callback);
|
||||
} else {
|
||||
// We are done immediately
|
||||
console.error("No conference handler");
|
||||
UI.messageHandler.showError('Error',
|
||||
'Unable to switch video stream.');
|
||||
callback();
|
||||
}
|
||||
},
|
||||
setVideoMute: function (mute, callback, options) {
|
||||
if(activecall && connection && RTC.localVideo)
|
||||
{
|
||||
activecall.setVideoMute(mute, callback, options);
|
||||
}
|
||||
},
|
||||
setAudioMute: function (mute, callback) {
|
||||
if (!(connection && RTC.localAudio)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (this.forceMuted && !mute) {
|
||||
console.info("Asking focus for unmute");
|
||||
connection.moderate.setMute(connection.emuc.myroomjid, mute);
|
||||
// FIXME: wait for result before resetting muted status
|
||||
this.forceMuted = false;
|
||||
}
|
||||
|
||||
if (mute == RTC.localAudio.isMuted()) {
|
||||
// Nothing to do
|
||||
return true;
|
||||
}
|
||||
|
||||
// It is not clear what is the right way to handle multiple tracks.
|
||||
// So at least make sure that they are all muted or all unmuted and
|
||||
// that we send presence just once.
|
||||
RTC.localAudio.mute();
|
||||
// isMuted is the opposite of audioEnabled
|
||||
connection.emuc.addAudioInfoToPresence(mute);
|
||||
connection.emuc.sendPresence();
|
||||
callback();
|
||||
return true;
|
||||
},
|
||||
// Really mute video, i.e. dont even send black frames
|
||||
muteVideo: function (pc, unmute) {
|
||||
// FIXME: this probably needs another of those lovely state safeguards...
|
||||
// which checks for iceconn == connected and sigstate == stable
|
||||
pc.setRemoteDescription(pc.remoteDescription,
|
||||
function () {
|
||||
pc.createAnswer(
|
||||
function (answer) {
|
||||
var sdp = new SDP(answer.sdp);
|
||||
if (sdp.media.length > 1) {
|
||||
if (unmute)
|
||||
sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
|
||||
else
|
||||
sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
|
||||
sdp.raw = sdp.session + sdp.media.join('');
|
||||
answer.sdp = sdp.raw;
|
||||
}
|
||||
pc.setLocalDescription(answer,
|
||||
function () {
|
||||
console.log('mute SLD ok');
|
||||
},
|
||||
function (error) {
|
||||
console.log('mute SLD error');
|
||||
UI.messageHandler.showError('Error',
|
||||
'Oops! Something went wrong and we failed to ' +
|
||||
'mute! (SLD Failure)');
|
||||
}
|
||||
);
|
||||
},
|
||||
function (error) {
|
||||
console.log(error);
|
||||
UI.messageHandler.showError();
|
||||
}
|
||||
);
|
||||
},
|
||||
function (error) {
|
||||
console.log('muteVideo SRD error');
|
||||
UI.messageHandler.showError('Error',
|
||||
'Oops! Something went wrong and we failed to stop video!' +
|
||||
'(SRD Failure)');
|
||||
|
||||
}
|
||||
);
|
||||
},
|
||||
toggleRecording: function (tokenEmptyCallback,
|
||||
startingCallback, startedCallback) {
|
||||
Recording.toggleRecording(tokenEmptyCallback,
|
||||
startingCallback, startedCallback);
|
||||
},
|
||||
addToPresence: function (name, value, dontSend) {
|
||||
switch (name)
|
||||
{
|
||||
case "displayName":
|
||||
connection.emuc.addDisplayNameToPresence(value);
|
||||
break;
|
||||
case "etherpad":
|
||||
connection.emuc.addEtherpadToPresence(value);
|
||||
break;
|
||||
case "prezi":
|
||||
connection.emuc.addPreziToPresence(value, 0);
|
||||
break;
|
||||
case "preziSlide":
|
||||
connection.emuc.addCurrentSlideToPresence(value);
|
||||
break;
|
||||
case "connectionQuality":
|
||||
connection.emuc.addConnectionInfoToPresence(value);
|
||||
break;
|
||||
case "email":
|
||||
connection.emuc.addEmailToPresence(value);
|
||||
default :
|
||||
console.log("Unknown tag for presence.");
|
||||
return;
|
||||
}
|
||||
if(!dontSend)
|
||||
connection.emuc.sendPresence();
|
||||
},
|
||||
sendLogs: function (data) {
|
||||
if(!focusMucJid)
|
||||
return;
|
||||
|
||||
var deflate = true;
|
||||
|
||||
var content = JSON.stringify(dataYes);
|
||||
if (deflate) {
|
||||
content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
|
||||
}
|
||||
content = Base64.encode(content);
|
||||
// XEP-0337-ish
|
||||
var message = $msg({to: focusMucJid, type: 'normal'});
|
||||
message.c('log', { xmlns: 'urn:xmpp:eventlog',
|
||||
id: 'PeerConnectionStats'});
|
||||
message.c('message').t(content).up();
|
||||
if (deflate) {
|
||||
message.c('tag', {name: "deflated", value: "true"}).up();
|
||||
}
|
||||
message.up();
|
||||
|
||||
connection.send(message);
|
||||
},
|
||||
populateData: function () {
|
||||
var data = {};
|
||||
if (connection.jingle) {
|
||||
data = connection.jingle.populateData();
|
||||
}
|
||||
return data;
|
||||
},
|
||||
getLogger: function () {
|
||||
if(connection.logger)
|
||||
return connection.logger.log;
|
||||
return null;
|
||||
},
|
||||
getPrezi: function () {
|
||||
return connection.emuc.getPrezi(this.myJid());
|
||||
},
|
||||
removePreziFromPresence: function () {
|
||||
connection.emuc.removePreziFromPresence();
|
||||
connection.emuc.sendPresence();
|
||||
},
|
||||
sendChatMessage: function (message, nickname) {
|
||||
connection.emuc.sendMessage(message, nickname);
|
||||
},
|
||||
setSubject: function (topic) {
|
||||
connection.emuc.setSubject(topic);
|
||||
},
|
||||
lockRoom: function (key, onSuccess, onError, onNotSupported) {
|
||||
connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
|
||||
},
|
||||
dial: function (to, from, roomName,roomPass) {
|
||||
connection.rayo.dial(to, from, roomName,roomPass);
|
||||
},
|
||||
setMute: function (jid, mute) {
|
||||
connection.moderate.setMute(jid, mute);
|
||||
},
|
||||
eject: function (jid) {
|
||||
connection.moderate.eject(jid);
|
||||
},
|
||||
findJidFromResource: function (resource) {
|
||||
connection.emuc.findJidFromResource(resource);
|
||||
},
|
||||
getMembers: function () {
|
||||
return connection.emuc.members;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = XMPP;
|
548
muc.js
548
muc.js
|
@ -1,548 +0,0 @@
|
|||
/* jshint -W117 */
|
||||
/* a simple MUC connection plugin
|
||||
* can only handle a single MUC room
|
||||
*/
|
||||
Strophe.addConnectionPlugin('emuc', {
|
||||
connection: null,
|
||||
roomjid: null,
|
||||
myroomjid: null,
|
||||
members: {},
|
||||
list_members: [], // so we can elect a new focus
|
||||
presMap: {},
|
||||
preziMap: {},
|
||||
joined: false,
|
||||
isOwner: false,
|
||||
role: null,
|
||||
init: function (conn) {
|
||||
this.connection = conn;
|
||||
},
|
||||
initPresenceMap: function (myroomjid) {
|
||||
this.presMap['to'] = myroomjid;
|
||||
this.presMap['xns'] = 'http://jabber.org/protocol/muc';
|
||||
},
|
||||
doJoin: function (jid, password) {
|
||||
this.myroomjid = jid;
|
||||
|
||||
console.info("Joined MUC as " + this.myroomjid);
|
||||
|
||||
this.initPresenceMap(this.myroomjid);
|
||||
|
||||
if (!this.roomjid) {
|
||||
this.roomjid = Strophe.getBareJidFromJid(jid);
|
||||
// add handlers (just once)
|
||||
this.connection.addHandler(this.onPresence.bind(this), null, 'presence', null, null, this.roomjid, {matchBare: true});
|
||||
this.connection.addHandler(this.onPresenceUnavailable.bind(this), null, 'presence', 'unavailable', null, this.roomjid, {matchBare: true});
|
||||
this.connection.addHandler(this.onPresenceError.bind(this), null, 'presence', 'error', null, this.roomjid, {matchBare: true});
|
||||
this.connection.addHandler(this.onMessage.bind(this), null, 'message', null, null, this.roomjid, {matchBare: true});
|
||||
}
|
||||
if (password !== undefined) {
|
||||
this.presMap['password'] = password;
|
||||
}
|
||||
this.sendPresence();
|
||||
},
|
||||
doLeave: function() {
|
||||
console.log("do leave", this.myroomjid);
|
||||
var pres = $pres({to: this.myroomjid, type: 'unavailable' });
|
||||
this.presMap.length = 0;
|
||||
this.connection.send(pres);
|
||||
},
|
||||
createNonAnonymousRoom: function() {
|
||||
// http://xmpp.org/extensions/xep-0045.html#createroom-reserved
|
||||
|
||||
var getForm = $iq({type: 'get', to: this.roomjid})
|
||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'})
|
||||
.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||
|
||||
this.connection.sendIQ(getForm, function (form){
|
||||
|
||||
if (!$(form).find(
|
||||
'>query>x[xmlns="jabber:x:data"]' +
|
||||
'>field[var="muc#roomconfig_whois"]').length) {
|
||||
|
||||
console.error('non-anonymous rooms not supported');
|
||||
return;
|
||||
}
|
||||
|
||||
var formSubmit = $iq({to: this.roomjid, type: 'set'})
|
||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
||||
|
||||
formSubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||
|
||||
formSubmit.c('field', {'var': 'FORM_TYPE'})
|
||||
.c('value')
|
||||
.t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
||||
|
||||
formSubmit.c('field', {'var': 'muc#roomconfig_whois'})
|
||||
.c('value').t('anyone').up().up();
|
||||
|
||||
this.connection.sendIQ(formSubmit);
|
||||
|
||||
}, function (error){
|
||||
console.error("Error getting room configuration form");
|
||||
});
|
||||
},
|
||||
onPresence: function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
|
||||
// What is this for? A workaround for something?
|
||||
if (pres.getAttribute('type')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Parse etherpad tag.
|
||||
var etherpad = $(pres).find('>etherpad');
|
||||
if (etherpad.length) {
|
||||
if (config.etherpad_base && !Moderator.isModerator()) {
|
||||
UI.initEtherpad(etherpad.text());
|
||||
}
|
||||
}
|
||||
|
||||
// Parse prezi tag.
|
||||
var presentation = $(pres).find('>prezi');
|
||||
if (presentation.length)
|
||||
{
|
||||
var url = presentation.attr('url');
|
||||
var current = presentation.find('>current').text();
|
||||
|
||||
console.log('presentation info received from', from, url);
|
||||
|
||||
if (this.preziMap[from] == null) {
|
||||
this.preziMap[from] = url;
|
||||
|
||||
$(document).trigger('presentationadded.muc', [from, url, current]);
|
||||
}
|
||||
else {
|
||||
$(document).trigger('gotoslide.muc', [from, url, current]);
|
||||
}
|
||||
}
|
||||
else if (this.preziMap[from] != null) {
|
||||
var url = this.preziMap[from];
|
||||
delete this.preziMap[from];
|
||||
$(document).trigger('presentationremoved.muc', [from, url]);
|
||||
}
|
||||
|
||||
// Parse audio info tag.
|
||||
var audioMuted = $(pres).find('>audiomuted');
|
||||
if (audioMuted.length) {
|
||||
$(document).trigger('audiomuted.muc', [from, audioMuted.text()]);
|
||||
}
|
||||
|
||||
// Parse video info tag.
|
||||
var videoMuted = $(pres).find('>videomuted');
|
||||
if (videoMuted.length) {
|
||||
$(document).trigger('videomuted.muc', [from, videoMuted.text()]);
|
||||
}
|
||||
|
||||
var stats = $(pres).find('>stats');
|
||||
if(stats.length)
|
||||
{
|
||||
var statsObj = {};
|
||||
Strophe.forEachChild(stats[0], "stat", function (el) {
|
||||
statsObj[el.getAttribute("name")] = el.getAttribute("value");
|
||||
});
|
||||
connectionquality.updateRemoteStats(from, statsObj);
|
||||
}
|
||||
|
||||
// Parse status.
|
||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="201"]').length) {
|
||||
this.isOwner = true;
|
||||
this.createNonAnonymousRoom();
|
||||
}
|
||||
|
||||
// Parse roles.
|
||||
var member = {};
|
||||
member.show = $(pres).find('>show').text();
|
||||
member.status = $(pres).find('>status').text();
|
||||
var tmp = $(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>item');
|
||||
member.affiliation = tmp.attr('affiliation');
|
||||
member.role = tmp.attr('role');
|
||||
|
||||
// Focus recognition
|
||||
member.jid = tmp.attr('jid');
|
||||
member.isFocus = false;
|
||||
if (member.jid
|
||||
&& member.jid.indexOf(Moderator.getFocusUserJid() + "/") == 0) {
|
||||
member.isFocus = true;
|
||||
}
|
||||
|
||||
var nicktag = $(pres).find('>nick[xmlns="http://jabber.org/protocol/nick"]');
|
||||
member.displayName = (nicktag.length > 0 ? nicktag.html() : null);
|
||||
|
||||
if (from == this.myroomjid) {
|
||||
if (member.affiliation == 'owner') this.isOwner = true;
|
||||
if (this.role !== member.role) {
|
||||
this.role = member.role;
|
||||
if(Moderator.onLocalRoleChange)
|
||||
Moderator.onLocalRoleChange(from, member, pres);
|
||||
UI.onLocalRoleChange(from, member, pres);
|
||||
}
|
||||
if (!this.joined) {
|
||||
this.joined = true;
|
||||
$(document).trigger('joined.muc', [from, member]);
|
||||
UI.onMucJoined(from, member);
|
||||
this.list_members.push(from);
|
||||
}
|
||||
} else if (this.members[from] === undefined) {
|
||||
// new participant
|
||||
this.members[from] = member;
|
||||
this.list_members.push(from);
|
||||
console.log('entered', from, member);
|
||||
if (member.isFocus)
|
||||
{
|
||||
focusMucJid = from;
|
||||
console.info("Ignore focus: " + from +", real JID: " + member.jid);
|
||||
}
|
||||
else {
|
||||
var id = $(pres).find('>userID').text();
|
||||
var email = $(pres).find('>email');
|
||||
if (email.length > 0) {
|
||||
id = email.text();
|
||||
}
|
||||
UI.onMucEntered(from, id, member.displayName);
|
||||
API.triggerEvent("participantJoined",{jid: from});
|
||||
}
|
||||
} else {
|
||||
// Presence update for existing participant
|
||||
// Watch role change:
|
||||
if (this.members[from].role != member.role) {
|
||||
this.members[from].role = member.role;
|
||||
UI.onMucRoleChanged(member.role, member.displayName);
|
||||
}
|
||||
}
|
||||
|
||||
// Always trigger presence to update bindings
|
||||
$(document).trigger('presence.muc', [from, member, pres]);
|
||||
|
||||
// Trigger status message update
|
||||
if (member.status) {
|
||||
UI.onMucPresenceStatus(from, member);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
onPresenceUnavailable: function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
// Status code 110 indicates that this notification is "self-presence".
|
||||
if (!$(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="110"]').length) {
|
||||
delete this.members[from];
|
||||
this.list_members.splice(this.list_members.indexOf(from), 1);
|
||||
this.onParticipantLeft(from);
|
||||
}
|
||||
// If the status code is 110 this means we're leaving and we would like
|
||||
// to remove everyone else from our view, so we trigger the event.
|
||||
else if (this.list_members.length > 1) {
|
||||
for (var i = 0; i < this.list_members.length; i++) {
|
||||
var member = this.list_members[i];
|
||||
delete this.members[i];
|
||||
this.list_members.splice(i, 1);
|
||||
this.onParticipantLeft(member);
|
||||
}
|
||||
}
|
||||
if ($(pres).find('>x[xmlns="http://jabber.org/protocol/muc#user"]>status[code="307"]').length) {
|
||||
$(document).trigger('kicked.muc', [from]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
onPresenceError: function (pres) {
|
||||
var from = pres.getAttribute('from');
|
||||
if ($(pres).find('>error[type="auth"]>not-authorized[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||
console.log('on password required', from);
|
||||
|
||||
UI.onPasswordReqiured(function (value) {
|
||||
connection.emuc.doJoin(from, value);
|
||||
})
|
||||
} else if ($(pres).find(
|
||||
'>error[type="cancel"]>not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
|
||||
var toDomain = Strophe.getDomainFromJid(pres.getAttribute('to'));
|
||||
if(toDomain === config.hosts.anonymousdomain) {
|
||||
// we are connected with anonymous domain and only non anonymous users can create rooms
|
||||
// we must authorize the user
|
||||
$(document).trigger('passwordrequired.main');
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
||||
pres);
|
||||
}
|
||||
} else {
|
||||
console.warn('onPresError ', pres);
|
||||
UI.messageHandler.openReportDialog(null,
|
||||
'Oops! Something went wrong and we couldn`t connect to the conference.',
|
||||
pres);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
sendMessage: function (body, nickname) {
|
||||
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
||||
msg.c('body', body).up();
|
||||
if (nickname) {
|
||||
msg.c('nick', {xmlns: 'http://jabber.org/protocol/nick'}).t(nickname).up().up();
|
||||
}
|
||||
this.connection.send(msg);
|
||||
API.triggerEvent("outgoingMessage", {"message": body});
|
||||
},
|
||||
setSubject: function (subject){
|
||||
var msg = $msg({to: this.roomjid, type: 'groupchat'});
|
||||
msg.c('subject', subject);
|
||||
this.connection.send(msg);
|
||||
console.log("topic changed to " + subject);
|
||||
},
|
||||
onMessage: function (msg) {
|
||||
// FIXME: this is a hack. but jingle on muc makes nickchanges hard
|
||||
var from = msg.getAttribute('from');
|
||||
var nick = $(msg).find('>nick[xmlns="http://jabber.org/protocol/nick"]').text() || Strophe.getResourceFromJid(from);
|
||||
|
||||
var txt = $(msg).find('>body').text();
|
||||
var type = msg.getAttribute("type");
|
||||
if(type == "error")
|
||||
{
|
||||
UI.chatAddError($(msg).find('>text').text(), txt);
|
||||
return true;
|
||||
}
|
||||
|
||||
var subject = $(msg).find('>subject');
|
||||
if(subject.length)
|
||||
{
|
||||
var subjectText = subject.text();
|
||||
if(subjectText || subjectText == "") {
|
||||
UI.chatSetSubject(subjectText);
|
||||
console.log("Subject is changed to " + subjectText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (txt) {
|
||||
console.log('chat', nick, txt);
|
||||
UI.updateChatConversation(from, nick, txt);
|
||||
if(from != this.myroomjid)
|
||||
API.triggerEvent("incomingMessage",
|
||||
{"from": from, "nick": nick, "message": txt});
|
||||
}
|
||||
return true;
|
||||
},
|
||||
lockRoom: function (key, onSuccess, onError, onNotSupported) {
|
||||
//http://xmpp.org/extensions/xep-0045.html#roomconfig
|
||||
var ob = this;
|
||||
this.connection.sendIQ($iq({to: this.roomjid, type: 'get'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'}),
|
||||
function (res) {
|
||||
if ($(res).find('>query>x[xmlns="jabber:x:data"]>field[var="muc#roomconfig_roomsecret"]').length) {
|
||||
var formsubmit = $iq({to: ob.roomjid, type: 'set'}).c('query', {xmlns: 'http://jabber.org/protocol/muc#owner'});
|
||||
formsubmit.c('x', {xmlns: 'jabber:x:data', type: 'submit'});
|
||||
formsubmit.c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#roomconfig').up().up();
|
||||
formsubmit.c('field', {'var': 'muc#roomconfig_roomsecret'}).c('value').t(key).up().up();
|
||||
// Fixes a bug in prosody 0.9.+ https://code.google.com/p/lxmppd/issues/detail?id=373
|
||||
formsubmit.c('field', {'var': 'muc#roomconfig_whois'}).c('value').t('anyone').up().up();
|
||||
// FIXME: is muc#roomconfig_passwordprotectedroom required?
|
||||
this.connection.sendIQ(formsubmit,
|
||||
onSuccess,
|
||||
onError);
|
||||
} else {
|
||||
onNotSupported();
|
||||
}
|
||||
}, onError);
|
||||
},
|
||||
kick: function (jid) {
|
||||
var kickIQ = $iq({to: this.roomjid, type: 'set'})
|
||||
.c('query', {xmlns: 'http://jabber.org/protocol/muc#admin'})
|
||||
.c('item', {nick: Strophe.getResourceFromJid(jid), role: 'none'})
|
||||
.c('reason').t('You have been kicked.').up().up().up();
|
||||
|
||||
this.connection.sendIQ(
|
||||
kickIQ,
|
||||
function (result) {
|
||||
console.log('Kick participant with jid: ', jid, result);
|
||||
},
|
||||
function (error) {
|
||||
console.log('Kick participant error: ', error);
|
||||
});
|
||||
},
|
||||
sendPresence: function () {
|
||||
var pres = $pres({to: this.presMap['to'] });
|
||||
pres.c('x', {xmlns: this.presMap['xns']});
|
||||
|
||||
if (this.presMap['password']) {
|
||||
pres.c('password').t(this.presMap['password']).up();
|
||||
}
|
||||
|
||||
pres.up();
|
||||
|
||||
// Send XEP-0115 'c' stanza that contains our capabilities info
|
||||
if (connection.caps) {
|
||||
connection.caps.node = config.clientNode;
|
||||
pres.c('c', connection.caps.generateCapsAttrs()).up();
|
||||
}
|
||||
|
||||
pres.c('user-agent', {xmlns: 'http://jitsi.org/jitmeet/user-agent'})
|
||||
.t(navigator.userAgent).up();
|
||||
|
||||
if(this.presMap['bridgeIsDown']) {
|
||||
pres.c('bridgeIsDown').up();
|
||||
}
|
||||
|
||||
if(this.presMap['email']) {
|
||||
pres.c('email').t(this.presMap['email']).up();
|
||||
}
|
||||
|
||||
if(this.presMap['userId']) {
|
||||
pres.c('userId').t(this.presMap['userId']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['displayName']) {
|
||||
// XEP-0172
|
||||
pres.c('nick', {xmlns: 'http://jabber.org/protocol/nick'})
|
||||
.t(this.presMap['displayName']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['audions']) {
|
||||
pres.c('audiomuted', {xmlns: this.presMap['audions']})
|
||||
.t(this.presMap['audiomuted']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['videons']) {
|
||||
pres.c('videomuted', {xmlns: this.presMap['videons']})
|
||||
.t(this.presMap['videomuted']).up();
|
||||
}
|
||||
|
||||
if(this.presMap['statsns'])
|
||||
{
|
||||
var stats = pres.c('stats', {xmlns: this.presMap['statsns']});
|
||||
for(var stat in this.presMap["stats"])
|
||||
if(this.presMap["stats"][stat] != null)
|
||||
stats.c("stat",{name: stat, value: this.presMap["stats"][stat]}).up();
|
||||
pres.up();
|
||||
}
|
||||
|
||||
if (this.presMap['prezins']) {
|
||||
pres.c('prezi',
|
||||
{xmlns: this.presMap['prezins'],
|
||||
'url': this.presMap['preziurl']})
|
||||
.c('current').t(this.presMap['prezicurrent']).up().up();
|
||||
}
|
||||
|
||||
if (this.presMap['etherpadns']) {
|
||||
pres.c('etherpad', {xmlns: this.presMap['etherpadns']})
|
||||
.t(this.presMap['etherpadname']).up();
|
||||
}
|
||||
|
||||
if (this.presMap['medians'])
|
||||
{
|
||||
pres.c('media', {xmlns: this.presMap['medians']});
|
||||
var sourceNumber = 0;
|
||||
Object.keys(this.presMap).forEach(function (key) {
|
||||
if (key.indexOf('source') >= 0) {
|
||||
sourceNumber++;
|
||||
}
|
||||
});
|
||||
if (sourceNumber > 0)
|
||||
for (var i = 1; i <= sourceNumber/3; i ++) {
|
||||
pres.c('source',
|
||||
{type: this.presMap['source' + i + '_type'],
|
||||
ssrc: this.presMap['source' + i + '_ssrc'],
|
||||
direction: this.presMap['source'+ i + '_direction']
|
||||
|| 'sendrecv' }
|
||||
).up();
|
||||
}
|
||||
}
|
||||
|
||||
pres.up();
|
||||
// console.debug(pres.toString());
|
||||
connection.send(pres);
|
||||
},
|
||||
addDisplayNameToPresence: function (displayName) {
|
||||
this.presMap['displayName'] = displayName;
|
||||
},
|
||||
addMediaToPresence: function (sourceNumber, mtype, ssrcs, direction) {
|
||||
if (!this.presMap['medians'])
|
||||
this.presMap['medians'] = 'http://estos.de/ns/mjs';
|
||||
|
||||
this.presMap['source' + sourceNumber + '_type'] = mtype;
|
||||
this.presMap['source' + sourceNumber + '_ssrc'] = ssrcs;
|
||||
this.presMap['source' + sourceNumber + '_direction'] = direction;
|
||||
},
|
||||
clearPresenceMedia: function () {
|
||||
var self = this;
|
||||
Object.keys(this.presMap).forEach( function(key) {
|
||||
if(key.indexOf('source') != -1) {
|
||||
delete self.presMap[key];
|
||||
}
|
||||
});
|
||||
},
|
||||
addPreziToPresence: function (url, currentSlide) {
|
||||
this.presMap['prezins'] = 'http://jitsi.org/jitmeet/prezi';
|
||||
this.presMap['preziurl'] = url;
|
||||
this.presMap['prezicurrent'] = currentSlide;
|
||||
},
|
||||
removePreziFromPresence: function () {
|
||||
delete this.presMap['prezins'];
|
||||
delete this.presMap['preziurl'];
|
||||
delete this.presMap['prezicurrent'];
|
||||
},
|
||||
addCurrentSlideToPresence: function (currentSlide) {
|
||||
this.presMap['prezicurrent'] = currentSlide;
|
||||
},
|
||||
getPrezi: function (roomjid) {
|
||||
return this.preziMap[roomjid];
|
||||
},
|
||||
addEtherpadToPresence: function(etherpadName) {
|
||||
this.presMap['etherpadns'] = 'http://jitsi.org/jitmeet/etherpad';
|
||||
this.presMap['etherpadname'] = etherpadName;
|
||||
},
|
||||
addAudioInfoToPresence: function(isMuted) {
|
||||
this.presMap['audions'] = 'http://jitsi.org/jitmeet/audio';
|
||||
this.presMap['audiomuted'] = isMuted.toString();
|
||||
},
|
||||
addVideoInfoToPresence: function(isMuted) {
|
||||
this.presMap['videons'] = 'http://jitsi.org/jitmeet/video';
|
||||
this.presMap['videomuted'] = isMuted.toString();
|
||||
},
|
||||
addConnectionInfoToPresence: function(stats) {
|
||||
this.presMap['statsns'] = 'http://jitsi.org/jitmeet/stats';
|
||||
this.presMap['stats'] = stats;
|
||||
},
|
||||
findJidFromResource: function(resourceJid) {
|
||||
if(resourceJid &&
|
||||
resourceJid === Strophe.getResourceFromJid(connection.emuc.myroomjid)) {
|
||||
return connection.emuc.myroomjid;
|
||||
}
|
||||
var peerJid = null;
|
||||
Object.keys(this.members).some(function (jid) {
|
||||
peerJid = jid;
|
||||
return Strophe.getResourceFromJid(jid) === resourceJid;
|
||||
});
|
||||
return peerJid;
|
||||
},
|
||||
addBridgeIsDownToPresence: function() {
|
||||
this.presMap['bridgeIsDown'] = true;
|
||||
},
|
||||
addEmailToPresence: function(email) {
|
||||
this.presMap['email'] = email;
|
||||
},
|
||||
addUserIdToPresence: function(userId) {
|
||||
this.presMap['userId'] = userId;
|
||||
},
|
||||
isModerator: function() {
|
||||
return this.role === 'moderator';
|
||||
},
|
||||
getMemberRole: function(peerJid) {
|
||||
if (this.members[peerJid]) {
|
||||
return this.members[peerJid].role;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onParticipantLeft: function (jid) {
|
||||
UI.onMucLeft(jid);
|
||||
|
||||
API.triggerEvent("participantLeft",{jid: jid});
|
||||
|
||||
delete jid2Ssrc[jid];
|
||||
|
||||
connection.jingle.terminateByJid(jid);
|
||||
|
||||
if (connection.emuc.getPrezi(jid)) {
|
||||
$(document).trigger('presentationremoved.muc',
|
||||
[jid, connection.emuc.getPrezi(jid)]);
|
||||
}
|
||||
|
||||
Moderator.onMucLeft(jid);
|
||||
}
|
||||
});
|
167
recording.js
167
recording.js
|
@ -1,167 +0,0 @@
|
|||
/* global $, $iq, config, connection, focusMucJid, messageHandler, Moderator,
|
||||
Toolbar, Util */
|
||||
var Recording = (function (my) {
|
||||
var recordingToken = null;
|
||||
var recordingEnabled;
|
||||
|
||||
/**
|
||||
* Whether to use a jirecon component for recording, or use the videobridge
|
||||
* through COLIBRI.
|
||||
*/
|
||||
var useJirecon = (typeof config.hosts.jirecon != "undefined");
|
||||
|
||||
/**
|
||||
* The ID of the jirecon recording session. Jirecon generates it when we
|
||||
* initially start recording, and it needs to be used in subsequent requests
|
||||
* to jirecon.
|
||||
*/
|
||||
var jireconRid = null;
|
||||
|
||||
my.setRecordingToken = function (token) {
|
||||
recordingToken = token;
|
||||
};
|
||||
|
||||
my.setRecording = function (state, token, callback) {
|
||||
if (useJirecon){
|
||||
this.setRecordingJirecon(state, token, callback);
|
||||
} else {
|
||||
this.setRecordingColibri(state, token, callback);
|
||||
}
|
||||
};
|
||||
|
||||
my.setRecordingJirecon = function (state, token, callback) {
|
||||
if (state == recordingEnabled){
|
||||
return;
|
||||
}
|
||||
|
||||
var iq = $iq({to: config.hosts.jirecon, type: 'set'})
|
||||
.c('recording', {xmlns: 'http://jitsi.org/protocol/jirecon',
|
||||
action: state ? 'start' : 'stop',
|
||||
mucjid: connection.emuc.roomjid});
|
||||
if (!state){
|
||||
iq.attrs({rid: jireconRid});
|
||||
}
|
||||
|
||||
console.log('Start recording');
|
||||
|
||||
connection.sendIQ(
|
||||
iq,
|
||||
function (result) {
|
||||
// TODO wait for an IQ with the real status, since this is
|
||||
// provisional?
|
||||
jireconRid = $(result).find('recording').attr('rid');
|
||||
console.log('Recording ' + (state ? 'started' : 'stopped') +
|
||||
'(jirecon)' + result);
|
||||
recordingEnabled = state;
|
||||
if (!state){
|
||||
jireconRid = null;
|
||||
}
|
||||
|
||||
callback(state);
|
||||
},
|
||||
function (error) {
|
||||
console.log('Failed to start recording, error: ', error);
|
||||
callback(recordingEnabled);
|
||||
});
|
||||
};
|
||||
|
||||
// Sends a COLIBRI message which enables or disables (according to 'state')
|
||||
// the recording on the bridge. Waits for the result IQ and calls 'callback'
|
||||
// with the new recording state, according to the IQ.
|
||||
my.setRecordingColibri = function (state, token, callback) {
|
||||
var elem = $iq({to: focusMucJid, type: 'set'});
|
||||
elem.c('conference', {
|
||||
xmlns: 'http://jitsi.org/protocol/colibri'
|
||||
});
|
||||
elem.c('recording', {state: state, token: token});
|
||||
|
||||
connection.sendIQ(elem,
|
||||
function (result) {
|
||||
console.log('Set recording "', state, '". Result:', result);
|
||||
var recordingElem = $(result).find('>conference>recording');
|
||||
var newState = ('true' === recordingElem.attr('state'));
|
||||
|
||||
recordingEnabled = newState;
|
||||
callback(newState);
|
||||
},
|
||||
function (error) {
|
||||
console.warn(error);
|
||||
callback(recordingEnabled);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
my.toggleRecording = function () {
|
||||
if (!Moderator.isModerator()) {
|
||||
console.log(
|
||||
'non-focus, or conference not yet organized:' +
|
||||
' not enabling recording');
|
||||
return;
|
||||
}
|
||||
|
||||
// Jirecon does not (currently) support a token.
|
||||
if (!recordingToken && !useJirecon)
|
||||
{
|
||||
UI.messageHandler.openTwoButtonDialog(null,
|
||||
'<h2>Enter recording token</h2>' +
|
||||
'<input id="recordingToken" type="text" ' +
|
||||
'placeholder="token" autofocus>',
|
||||
false,
|
||||
"Save",
|
||||
function (e, v, m, f) {
|
||||
if (v) {
|
||||
var token = document.getElementById('recordingToken');
|
||||
|
||||
if (token.value) {
|
||||
my.setRecordingToken(
|
||||
Util.escapeHtml(token.value));
|
||||
my.toggleRecording();
|
||||
}
|
||||
}
|
||||
},
|
||||
function (event) {
|
||||
document.getElementById('recordingToken').focus();
|
||||
},
|
||||
function () {}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var oldState = recordingEnabled;
|
||||
UI.setRecordingButtonState(!oldState);
|
||||
my.setRecording(!oldState,
|
||||
recordingToken,
|
||||
function (state) {
|
||||
console.log("New recording state: ", state);
|
||||
if (state === oldState)
|
||||
{
|
||||
// FIXME: new focus:
|
||||
// this will not work when moderator changes
|
||||
// during active session. Then it will assume that
|
||||
// recording status has changed to true, but it might have
|
||||
// been already true(and we only received actual status from
|
||||
// the focus).
|
||||
//
|
||||
// SO we start with status null, so that it is initialized
|
||||
// here and will fail only after second click, so if invalid
|
||||
// token was used we have to press the button twice before
|
||||
// current status will be fetched and token will be reset.
|
||||
//
|
||||
// Reliable way would be to return authentication error.
|
||||
// Or status update when moderator connects.
|
||||
// Or we have to stop recording session when current
|
||||
// moderator leaves the room.
|
||||
|
||||
// Failed to change, reset the token because it might
|
||||
// have been wrong
|
||||
my.setRecordingToken(null);
|
||||
}
|
||||
// Update with returned status
|
||||
UI.setRecordingButtonState(state);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return my;
|
||||
}(Recording || {}));
|
|
@ -0,0 +1,14 @@
|
|||
var XMPPEvents = {
|
||||
CONFERENCE_CERATED: "xmpp.conferenceCreated.jingle",
|
||||
CALL_TERMINATED: "xmpp.callterminated.jingle",
|
||||
CALL_INCOMING: "xmpp.callincoming.jingle",
|
||||
DISPOSE_CONFERENCE: "xmpp.dispoce_confernce",
|
||||
KICKED: "xmpp.kicked",
|
||||
BRIDGE_DOWN: "xmpp.bridge_down",
|
||||
USER_ID_CHANGED: "xmpp.user_id_changed",
|
||||
CHANGED_STREAMS: "xmpp.changed_streams",
|
||||
MUC_JOINED: "xmpp.muc_joined",
|
||||
DISPLAY_NAME_CHANGED: "xmpp.display_name_changed",
|
||||
REMOTE_STATS: "xmpp.remote_stats"
|
||||
};
|
||||
//module.exports = XMPPEvents;
|
Loading…
Reference in New Issue