Adds mute and kick functoinality avaialable for the focus of the conference.

This commit is contained in:
yanas 2014-05-12 00:41:58 +02:00
parent ca1fc93b2d
commit 56424df0a0
11 changed files with 384 additions and 31 deletions

176
app.js
View File

@ -15,6 +15,7 @@ var ssrc2jid = {};
*/
var ssrc2videoType = {};
var videoSrcToSsrc = {};
var mutedAudios = {};
var localVideoSrc = null;
var flipXLocalVideo = true;
@ -35,6 +36,8 @@ var getVideoPosition;
/* window.onbeforeunload = closePageWarning; */
var preMuted = false;
function init() {
RTC = setupRTC();
if (RTC === null) {
@ -179,6 +182,10 @@ function change_local_audio(stream) {
RTC.attachMediaStream($('#localAudio'), stream);
document.getElementById('localAudio').autoplay = true;
document.getElementById('localAudio').volume = 0;
if (preMuted) {
toggleAudio();
preMuted = false;
}
}
function change_local_video(stream, flipX) {
@ -485,8 +492,13 @@ $(document).bind('callactive.jingle', function (event, videoelem, sid) {
}
});
$(document).bind('callterminated.jingle', function (event, sid, reason) {
// FIXME
$(document).bind('callterminated.jingle', function (event, sid, jid, reason) {
// Leave the room if my call has been remotely terminated.
if (connection.emuc.joined && focus == null && reason === 'kick') {
connection.emuc.doLeave();
openMessageDialog( "Session Terminated",
"Ouch! You have been kicked out of the meet!");
}
});
$(document).bind('setLocalDescription.jingle', function (event, sid) {
@ -579,15 +591,25 @@ $(document).bind('entered.muc', function (event, jid, info, pres) {
});
$(document).bind('left.muc', function (event, jid) {
console.log('left', jid);
console.log('left.muc', jid);
// Need to call this with a slight delay, otherwise the element couldn't be
// found for some reason.
window.setTimeout(function () {
var container = document.getElementById(
'participant_' + Strophe.getResourceFromJid(jid));
if (container) {
// hide here, wait for video to close before removing
$(container).hide();
resizeThumbnails();
}
}, 10);
connection.jingle.terminateByJid(jid);
var container = document.getElementById('participant_' + Strophe.getResourceFromJid(jid));
if (container) {
// hide here, wait for video to close before removing
$(container).hide();
resizeThumbnails();
}
if (focus === null && connection.emuc.myroomjid === connection.emuc.list_members[0]) {
if (focus == null
&& jid !== connection.emuc.myroomjid
&& connection.emuc.myroomjid === connection.emuc.list_members[0]
&& connection.emuc.list_members.length > 1) {
console.log('welcome to our new focus... myself');
focus = new ColibriFocus(connection, config.hosts.bridge);
if (Object.keys(connection.emuc.members).length > 0) {
@ -603,7 +625,8 @@ $(document).bind('left.muc', function (event, jid) {
focus = new ColibriFocus(connection, config.hosts.bridge);
}
if (connection.emuc.getPrezi(jid)) {
$(document).trigger('presentationremoved.muc', [jid, connection.emuc.getPrezi(jid)]);
$(document).trigger('presentationremoved.muc',
[jid, connection.emuc.getPrezi(jid)]);
}
});
@ -686,6 +709,11 @@ $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
}
if (focus) {
mutedAudios[jid] = isMuted;
updateRemoteVideoMenu(jid, isMuted);
}
if (videoSpanId)
showAudioIndicator(videoSpanId, isMuted);
});
@ -700,7 +728,7 @@ $(document).bind('videomuted.muc', function (event, jid, isMuted) {
}
if (videoSpanId)
showAudioIndicator(videoSpanId, isMuted);
showVideoIndicator(videoSpanId, isMuted);
});
/**
@ -821,8 +849,11 @@ function toggleVideo() {
* Mutes / unmutes audio for the local participant.
*/
function toggleAudio() {
if (!(connection && connection.jingle.localAudio))
if (!(connection && connection.jingle.localAudio)) {
preMuted = true;
return;
}
var localAudio = connection.jingle.localAudio;
for (var idx = 0; idx < localAudio.getAudioTracks().length; idx++) {
var audioEnabled = localAudio.getAudioTracks()[idx].enabled;
@ -831,6 +862,8 @@ function toggleAudio() {
connection.emuc.addAudioInfoToPresence(audioEnabled); //isMuted is the opposite of audioEnabled
connection.emuc.sendPresence();
}
buttonClick("#mute", "icon-microphone icon-mic-disabled");
}
/**
@ -1167,6 +1200,22 @@ function buttonClick(id, classname) {
$(id).toggleClass(classname); // add the class to the clicked element
}
/**
* Shows a message to the user.
*
* @param titleString the title of the message
* @param messageString the text of the message
*/
function openMessageDialog(titleString, messageString) {
console.log("OPEN MESSAGE DIALOG");
$.prompt(messageString,
{
title: titleString,
persistent: false
}
);
}
/**
* Opens the lock room dialog.
*/
@ -1448,7 +1497,7 @@ function ensurePeerContainerExists(peerJid) {
return;
}
var container = addRemoteVideoContainer(videoSpanId);
var container = addRemoteVideoContainer(peerJid, videoSpanId);
var nickfield = document.createElement('span');
nickfield.className = "nick";
@ -1457,11 +1506,15 @@ function ensurePeerContainerExists(peerJid) {
resizeThumbnails();
}
function addRemoteVideoContainer(id) {
function addRemoteVideoContainer(peerJid, spanId) {
var container = document.createElement('span');
container.id = id;
container.id = spanId;
container.className = 'videocontainer';
var remotes = document.getElementById('remoteVideos');
if (focus)
addRemoteVideoMenu(peerJid, container);
remotes.appendChild(container);
return container;
}
@ -1476,6 +1529,97 @@ function createFocusIndicatorElement(parentElement) {
parentElement.appendChild(focusIndicator);
}
function addRemoteVideoMenu(jid, parentElement) {
var spanElement = document.createElement('span');
spanElement.className = 'remotevideomenu';
parentElement.appendChild(spanElement);
var menuElement = document.createElement('i');
menuElement.className = 'fa fa-angle-down';
menuElement.title = 'Remote user controls';
spanElement.appendChild(menuElement);
// <ul class="popupmenu">
// <li><a href="#">Mute</a></li>
// <li><a href="#">Eject</a></li>
// </ul>
var popupmenuElement = document.createElement('ul');
popupmenuElement.className = 'popupmenu';
popupmenuElement.id = 'remote_popupmenu_' + Strophe.getResourceFromJid(jid);
spanElement.appendChild(popupmenuElement);
var muteMenuItem = document.createElement('li');
var muteLinkItem = document.createElement('a');
var mutedIndicator = "<i class='icon-mic-disabled'></i>";
if (!mutedAudios[jid]) {
muteLinkItem.innerHTML = mutedIndicator + 'Mute';
muteLinkItem.className = 'mutelink';
}
else {
muteLinkItem.innerHTML = mutedIndicator + ' Muted';
muteLinkItem.className = 'mutelink disabled';
}
muteLinkItem.onclick = function(){
if ($(this).attr('disabled') != undefined) {
event.preventDefault();
}
var isMute = !mutedAudios[jid];
connection.moderate.setMute(jid, isMute);
popupmenuElement.setAttribute('style', 'display:none;');
if (isMute) {
this.innerHTML = mutedIndicator + ' Muted';
this.className = 'mutelink disabled';
}
else {
this.innerHTML = mutedIndicator + ' Mute';
this.className = 'mutelink';
}
};
muteMenuItem.appendChild(muteLinkItem);
popupmenuElement.appendChild(muteMenuItem);
var ejectIndicator = "<i class='fa fa-eject'></i>";
var ejectMenuItem = document.createElement('li');
var ejectLinkItem = document.createElement('a');
ejectLinkItem.innerHTML = ejectIndicator + ' Kick out';
ejectLinkItem.onclick = function(){
connection.moderate.eject(jid);
popupmenuElement.setAttribute('style', 'display:none;');
};
ejectMenuItem.appendChild(ejectLinkItem);
popupmenuElement.appendChild(ejectMenuItem);
}
function updateRemoteVideoMenu(jid, isMuted) {
var muteMenuItem
= $('#remote_popupmenu_'
+ Strophe.getResourceFromJid(jid)
+ '>li>a.mutelink');
var mutedIndicator = "<i class='icon-mic-disabled'></i>";
if (muteMenuItem.length) {
var muteLink = muteMenuItem.get(0);
if (isMuted === 'true') {
muteLink.innerHTML = mutedIndicator + ' Muted';
muteLink.className = 'mutelink disabled';
}
else {
muteLink.innerHTML = mutedIndicator + ' Mute';
muteLink.className = 'mutelink';
}
}
}
/**
* Toggles the application in and out of full screen mode
* (a.k.a. presentation mode in Chrome).

View File

@ -205,7 +205,6 @@ button {
}
button, input, select, textarea {
font-size: 100%;
margin: 0;
vertical-align: baseline;
}

56
css/popup_menu.css Normal file
View File

@ -0,0 +1,56 @@
/*Initialize*/
ul.popupmenu {
display:none;
position: absolute;
padding:0;
margin: 0;
bottom: 20px;
padding-bottom: 5px;
padding-top: 5px;
right: 10px;
left: 0px;
width: 100px;
background-color: rgba(0,0,0,1);
-webkit-box-shadow: 0 0 2px #000000, 0 0 10px #000000;
border-radius:8px;
}
ul.popupmenu:after {
content: url('../images/popupPointer.png');
display: block;
position: absolute;
bottom: -9px;
left: 13px;
}
ul.popupmenu li {
list-style-type: none;
text-align: left;
}
ul.popupmenu li:hover {
background-color: rgba(82, 82, 82, .7);
border-radius:2px;
}
/*Link Appearance*/
ul.popupmenu li a {
text-decoration: none;
color: #fff;
padding: 5px;
display: inline-block;
font-size: 9pt;
}
ul.popupmenu li a i {
width: 12px;
}
span.remotevideomenu:hover ul.popupmenu {
display:block !important;
}
a.disabled {
color: gray !important;
pointer-events: none;
}

View File

@ -16,9 +16,8 @@
left: 0;
right: 0;
width:auto;
overflow: hidden;
border:1px solid transparent;
z-index: 2;
z-index: 5;
}
.videocontainer {
@ -41,8 +40,8 @@
content:"";
cursor: pointer;
cursor: hand;
transform:scale(1.08, 1.08);
-webkit-transform:scale(1.08, 1.08);
/* transform:scale(1.08, 1.08);
-webkit-transform:scale(1.08, 1.08); */
transition-duration: 0.5s;
-webkit-transition-duration: 0.5s;
-webkit-animation-name: greyPulse;
@ -107,7 +106,8 @@
display: none;
}
#remoteVideos .videocontainer>span.focusindicator {
#remoteVideos .videocontainer>span.focusindicator,
#remoteVideos .videocontainer>span.remotevideomenu {
display: inline-block;
position: absolute;
color: #FFFFFF;

BIN
images/popupPointer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -31,6 +31,7 @@
<script src="prezi.js?v=2"></script><!-- prezi plugin -->
<script src="smileys.js?v=1"></script><!-- smiley images -->
<script src="replacement.js?v=5"></script><!-- link and smiley replacement -->
<script src="moderatemuc.js?v=1"></script><!-- moderator plugin -->
<script src="analytics.js?v=1"></script><!-- google analytics plugin -->
<link href="//netdna.bootstrapcdn.com/font-awesome/4.0.3/css/font-awesome.css" rel="stylesheet">
<link rel="stylesheet" href="css/font.css"/>
@ -38,6 +39,7 @@
<link rel="stylesheet" type="text/css" media="screen" href="css/videolayout_default.css?v=4" id="videolayout_default"/>
<link rel="stylesheet" href="css/jquery-impromptu.css?v=4">
<link rel="stylesheet" href="css/modaldialog.css?v=3">
<link rel="stylesheet" href="css/popup_menu.css?v=1">
<!--
Link used for inline installation of chrome desktop streaming extension,
is updated automatically from the code with the value defined in config.js -->
@ -49,7 +51,7 @@
<body>
<div id="header">
<span id="toolbar">
<a class="button" onclick='buttonClick("#mute", "icon-microphone icon-mic-disabled");toggleAudio();'>
<a class="button" onclick='toggleAudio();'>
<i id="mute" title="Mute / unmute" class="icon-microphone"></i></a>
<div class="header_button_separator"></div>
<a class="button" onclick='buttonClick("#video", "icon-camera icon-camera-disabled");toggleVideo();'>

View File

@ -848,3 +848,46 @@ ColibriFocus.prototype.terminate = function (session, reason) {
delete this.remotessrc[session.peerjid];
this.modifySources();
};
ColibriFocus.prototype.sendTerminate = function (session, reason, text) {
var term = $iq({to: session.peerjid, type: 'set'})
.c('jingle',
{xmlns: 'urn:xmpp:jingle:1',
action: 'session-terminate',
initiator: session.me,
sid: session.sid})
.c('reason')
.c(reason || 'success');
if (text) {
term.up().c('text').t(text);
}
this.connection.sendIQ(term,
function () {
if (!session)
return;
if (session.peerconnection) {
session.peerconnection.close();
session.peerconnection = null;
}
session.terminate();
var ack = {};
ack.source = 'terminate';
$(document).trigger('ack.jingle', [session.sid, ack]);
},
function (stanza) {
var error = ($(stanza).find('error').length) ? {
code: $(stanza).find('error').attr('code'),
reason: $(stanza).find('error :first')[0].tagName,
}:{};
$(document).trigger('ack.jingle', [self.sid, error]);
},
10000);
if (this.statsinterval !== null) {
window.clearInterval(this.statsinterval);
this.statsinterval = null;
}
};

View File

@ -90,5 +90,5 @@ ColibriSession.prototype.sendAnswer = function (sdp, provisional) {
};
ColibriSession.prototype.sendTerminate = function (reason, text) {
console.log('ColibriSession.sendTerminate');
this.colibri.sendTerminate(this, reason, text);
};

View File

@ -112,17 +112,27 @@ Strophe.addConnectionPlugin('jingle', {
$(document).trigger('callaccepted.jingle', [sess.sid]);
break;
case 'session-terminate':
console.log('terminating...');
// 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]);
$(document).trigger('callterminated.jingle',
[sess.sid, sess.peerjid]);
}
break;
case 'transport-info':
@ -192,6 +202,7 @@ Strophe.addConnectionPlugin('jingle', {
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];
@ -200,7 +211,22 @@ Strophe.addConnectionPlugin('jingle', {
console.log('peer went away silently', jid);
delete this.sessions[sess.sid];
delete this.jid2session[jid];
$(document).trigger('callterminated.jingle', [sess.sid, 'gone']);
$(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']);
}
}
},

50
moderatemuc.js Normal file
View File

@ -0,0 +1,50 @@
/**
* Moderate connection plugin.
*/
Strophe.addConnectionPlugin('moderate', {
connection: null,
roomjid: null,
myroomjid: null,
members: {},
list_members: [], // so we can elect a new focus
presMap: {},
preziMap: {},
joined: false,
isOwner: false,
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) {
var iq = $iq({to: jid, type: 'set'})
.c('mute', {xmlns: 'http://jitsi.org/jitmeet/audio'})
.t(mute.toString())
.up();
this.connection.sendIQ(
iq,
function (result) {
console.log('set mute', result);
},
function (error) {
console.log('set mute error', error);
});
},
onMute: function(iq) {
var mute = $(iq).find('mute');
if (mute.length) {
toggleAudio();
}
return true;
},
eject: function(jid) {
connection.jingle.terminateRemoteByJid(jid, 'kick');
connection.emuc.kick(jid);
}
});

41
muc.js
View File

@ -36,6 +36,12 @@ Strophe.addConnectionPlugin('emuc', {
}
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);
},
onPresence: function (pres) {
var from = pres.getAttribute('from');
var type = pres.getAttribute('type');
@ -125,9 +131,22 @@ Strophe.addConnectionPlugin('emuc', {
},
onPresenceUnavailable: function (pres) {
var from = pres.getAttribute('from');
delete this.members[from];
this.list_members.splice(this.list_members.indexOf(from), 1);
$(document).trigger('left.muc', [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);
$(document).trigger('left.muc', [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);
$(document).trigger('left.muc', member);
}
}
return true;
},
onPresenceError: function (pres) {
@ -188,6 +207,21 @@ Strophe.addConnectionPlugin('emuc', {
}
);
},
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']});
@ -210,7 +244,6 @@ Strophe.addConnectionPlugin('emuc', {
}
if (this.presMap['videons']) {
console.log("SEND VIDEO MUTED", this.presMap['videomuted']);
pres.c('videomuted', {xmlns: this.presMap['videons']})
.t(this.presMap['videomuted']).up();
}