Implements audio and visual chat notifications. Closes issue #7. Implements audio notifications for people joining and leaving. Closes issue #37.

This commit is contained in:
yanas 2014-02-18 20:11:27 +01:00
parent 9deae2e6cd
commit ee5936aaef
10 changed files with 340 additions and 164 deletions

156
app.js
View File

@ -182,6 +182,8 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
container = document.createElement('span'); container = document.createElement('span');
container.className = 'videocontainer'; container.className = 'videocontainer';
remotes.appendChild(container); remotes.appendChild(container);
console.log("PLAY USER JOINEDDDDDDDD");
Util.playSoundNotification('userJoined');
} }
var vid = document.createElement('video'); var vid = document.createElement('video');
var id = 'remoteVideo_' + sid + '_' + data.stream.id; var id = 'remoteVideo_' + sid + '_' + data.stream.id;
@ -212,11 +214,12 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
if (pick) { if (pick) {
if (pick.src === localVideoSrc) if (pick.src === localVideoSrc)
isLocalVideo = true; isLocalVideo = true;
updateLargeVideo(pick.src, isLocalVideo, pick.volume); updateLargeVideo(pick.src, isLocalVideo, pick.volume);
} }
} }
$('#' + id).parent().remove(); $('#' + id).parent().remove();
Util.playSoundNotification('userLeft');
resizeThumbnails(); resizeThumbnails();
}; };
sel.click( sel.click(
@ -632,8 +635,8 @@ function toggleAudio() {
} }
} }
function resizeLarge() { var resizeLarge = function () {
resizeChat(); Chat.resizeChat();
var availableHeight = window.innerHeight; var availableHeight = window.innerHeight;
var chatspaceWidth = $('#chatspace').is(":visible") var chatspaceWidth = $('#chatspace').is(":visible")
? $('#chatspace').width() ? $('#chatspace').width()
@ -666,7 +669,7 @@ function resizeLarge() {
} }
resizeThumbnails(); resizeThumbnails();
} };
function resizeThumbnails() { function resizeThumbnails() {
// Calculate the available height, which is the inner window height minus 39px for the header // Calculate the available height, which is the inner window height minus 39px for the header
@ -691,70 +694,8 @@ function resizeThumbnails() {
$('#remoteVideos>span').height(availableHeight); $('#remoteVideos>span').height(availableHeight);
} }
function resizeChat() {
var availableHeight = window.innerHeight;
var availableWidth = window.innerWidth;
var chatWidth = 200;
if (availableWidth*0.2 < 200)
chatWidth = availableWidth*0.2;
$('#chatspace').width(chatWidth);
$('#chatspace').height(availableHeight - 40);
resizeChatConversation();
}
function resizeChatConversation() {
var usermsgStyleHeight = document.getElementById("usermsg").style.height;
var usermsgHeight = usermsgStyleHeight.substring(0, usermsgStyleHeight.indexOf('px'));
$('#chatconversation').width($('#chatspace').width() - 10);
$('#chatconversation').height(window.innerHeight - 50 - parseInt(usermsgHeight));
}
$(document).ready(function () { $(document).ready(function () {
var storedDisplayName = window.localStorage.displayname; Chat.init();
if (storedDisplayName) {
nickname = storedDisplayName;
setChatConversationMode(true);
}
$('#nickinput').keydown(function(event) {
if (event.keyCode == 13) {
event.preventDefault();
var val = this.value;
this.value = '';
if (!nickname) {
nickname = val;
window.localStorage.displayname = nickname;
connection.emuc.addDisplayNameToPresence(nickname);
connection.emuc.sendPresence();
setChatConversationMode(true);
return;
}
}
});
$('#usermsg').keydown(function(event) {
if (event.keyCode == 13) {
event.preventDefault();
var message = this.value;
$('#usermsg').val('').trigger('autosize.resize');
this.focus();
connection.emuc.sendMessage(message, nickname);
}
});
var onTextAreaResize = function() {
resizeChatConversation();
scrollChatToBottom();
};
$('#usermsg').autosize({callback: onTextAreaResize});
// Set the defaults for prompt dialogs. // Set the defaults for prompt dialogs.
jQuery.prompt.setDefaults({persistent: false}); jQuery.prompt.setDefaults({persistent: false});
@ -826,24 +767,6 @@ function dump(elem, filename){
return false; return false;
} }
/*
* Appends the given message to the chat conversation.
*/
function updateChatConversation(nick, message)
{
var divClassName = '';
if (nickname == nick)
divClassName = "localuser";
else
divClassName = "remoteuser";
//replace links and smileys
message = processReplacements(message);
$('#chatconversation').append('<div class="' + divClassName + '"><b>' + nick + ': </b>' + message + '</div>');
$('#chatconversation').animate({ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
}
/* /*
* Changes the style class of the element given by id. * Changes the style class of the element given by id.
*/ */
@ -1092,44 +1015,6 @@ function updateLockButton() {
buttonClick("#lockIcon", "fa fa-unlock fa-lg fa fa-lock fa-lg"); buttonClick("#lockIcon", "fa fa-unlock fa-lg fa fa-lock fa-lg");
} }
/*
* Opens / closes the chat area.
*/
function openChat() {
var chatspace = $('#chatspace');
var videospace = $('#videospace');
var onShow = function () {
resizeLarge();
$('#chatspace').show("slide", { direction: "right", duration: 500});
};
var onHide = function () {
$('#chatspace').hide("slide", { direction: "right", duration: 500});
resizeLarge();
};
if (chatspace.css("display") == 'block') {
videospace.animate({right: 0}, {queue: false, duration: 500, progress: onHide});
}
else {
videospace.animate({right: chatspace.width()},
{queue: false,
duration: 500,
progress: onShow,
complete: function() {
scrollChatToBottom();
}
});
}
// Request the focus in the nickname field or the chat input field.
if ($('#nickname').css('visibility') == 'visible')
$('#nickinput').focus();
else {
$('#usermsg').focus();
}
}
/* /*
* Shows the call main toolbar. * Shows the call main toolbar.
*/ */
@ -1199,7 +1084,7 @@ function addRemoteVideoContainer(id) {
return container; return container;
} }
/* /**
* Creates the element indicating the focus of the conference. * Creates the element indicating the focus of the conference.
*/ */
function createFocusIndicatorElement(parentElement) { function createFocusIndicatorElement(parentElement) {
@ -1209,13 +1094,7 @@ function createFocusIndicatorElement(parentElement) {
parentElement.appendChild(focusIndicator); parentElement.appendChild(focusIndicator);
} }
function scrollChatToBottom() { /**
setTimeout(function() {
$('#chatconversation').scrollTop($('#chatconversation')[0].scrollHeight);
}, 5);
}
/*
* Toggles the application in and out of full screen mode * Toggles the application in and out of full screen mode
* (a.k.a. presentation mode in Chrome). * (a.k.a. presentation mode in Chrome).
*/ */
@ -1246,7 +1125,7 @@ function toggleFullScreen() {
} }
/** /**
* * Shows the display name for the given video.
*/ */
function showDisplayName(videoSpanId, displayName) { function showDisplayName(videoSpanId, displayName) {
var nameSpan = $('#' + videoSpanId + '>span.displayname'); var nameSpan = $('#' + videoSpanId + '>span.displayname');
@ -1306,7 +1185,7 @@ function showDisplayName(videoSpanId, displayName) {
connection.emuc.addDisplayNameToPresence(nickname); connection.emuc.addDisplayNameToPresence(nickname);
connection.emuc.sendPresence(); connection.emuc.sendPresence();
setChatConversationMode(true); Chat.setChatConversationMode(true);
} }
if (!$('#localDisplayName').is(":visible")) { if (!$('#localDisplayName').is(":visible")) {
@ -1337,13 +1216,4 @@ function createEditDisplayNameButton() {
editButton.innerHTML = '<i class="fa fa-pencil"></i>'; editButton.innerHTML = '<i class="fa fa-pencil"></i>';
return editButton; return editButton;
} }
function setChatConversationMode(isConversationMode) {
if (isConversationMode) {
$('#nickname').css({visibility:"hidden"});
$('#chatconversation').css({visibility:'visible'});
$('#usermsg').css({visibility:'visible'});
$('#usermsg').focus();
}
}

222
chat.js Normal file
View File

@ -0,0 +1,222 @@
/**
* Chat related user interface.
*/
var Chat = (function (my) {
var notificationInterval = false;
var unreadMessages = 0;
/**
* Initializes chat related interface.
*/
my.init = function () {
var storedDisplayName = window.localStorage.displayname;
if (storedDisplayName) {
nickname = storedDisplayName;
Chat.setChatConversationMode(true);
}
$('#nickinput').keydown(function(event) {
if (event.keyCode == 13) {
event.preventDefault();
var val = this.value;
this.value = '';
if (!nickname) {
nickname = val;
window.localStorage.displayname = nickname;
connection.emuc.addDisplayNameToPresence(nickname);
connection.emuc.sendPresence();
Chat.setChatConversationMode(true);
return;
}
}
});
$('#usermsg').keydown(function(event) {
if (event.keyCode == 13) {
event.preventDefault();
var message = this.value;
$('#usermsg').val('').trigger('autosize.resize');
this.focus();
connection.emuc.sendMessage(message, nickname);
}
});
var onTextAreaResize = function() {
resizeChatConversation();
scrollChatToBottom();
};
$('#usermsg').autosize({callback: onTextAreaResize});
$("#chatspace").bind("shown",
function() {
unreadMessages = 0;
setVisualNotification(false);
});
};
/**
* Appends the given message to the chat conversation.
*/
my.updateChatConversation = function (nick, message) {
var divClassName = '';
if (nickname == nick) {
divClassName = "localuser";
}
else {
divClassName = "remoteuser";
if (!$('#chatspace').is(":visible")) {
unreadMessages++;
Util.playSoundNotification('chatNotification');
setVisualNotification(true);
}
}
//replace links and smileys
message = processReplacements(message);
$('#chatconversation').append('<div class="' + divClassName + '"><b>'
+ nick + ': </b>' + message + '</div>');
$('#chatconversation').animate(
{ scrollTop: $('#chatconversation')[0].scrollHeight}, 1000);
};
/**
* Opens / closes the chat area.
*/
my.toggleChat = function () {
var chatspace = $('#chatspace');
var videospace = $('#videospace');
var onShow = function () {
resizeLarge();
$('#chatspace').show("slide", { direction: "right", duration: 500});
};
var onHide = function () {
$('#chatspace').hide("slide", { direction: "right", duration: 500});
resizeLarge();
};
if (chatspace.is(":visible")) {
videospace.animate( {right: 0},
{queue: false,
duration: 500,
progress: onHide});
}
else {
videospace.animate({right: chatspace.width()},
{queue: false,
duration: 500,
progress: onShow,
complete: function() {
scrollChatToBottom();
chatspace.trigger('shown');
}
});
}
// Request the focus in the nickname field or the chat input field.
if ($('#nickname').css('visibility') == 'visible')
$('#nickinput').focus();
else {
$('#usermsg').focus();
}
};
/**
* Sets the chat conversation mode.
*/
my.setChatConversationMode = function (isConversationMode) {
if (isConversationMode) {
$('#nickname').css({visibility:"hidden"});
$('#chatconversation').css({visibility:'visible'});
$('#usermsg').css({visibility:'visible'});
$('#usermsg').focus();
}
};
/**
* Resizes the chat area.
*/
my.resizeChat = function () {
var availableHeight = window.innerHeight;
var availableWidth = window.innerWidth;
var chatWidth = 200;
if (availableWidth*0.2 < 200)
chatWidth = availableWidth*0.2;
$('#chatspace').width(chatWidth);
$('#chatspace').height(availableHeight - 40);
resizeChatConversation();
};
/**
* Resizes the chat conversation.
*/
function resizeChatConversation() {
var usermsgStyleHeight = document.getElementById("usermsg").style.height;
var usermsgHeight = usermsgStyleHeight
.substring(0, usermsgStyleHeight.indexOf('px'));
$('#chatconversation').width($('#chatspace').width() - 10);
$('#chatconversation')
.height(window.innerHeight - 50 - parseInt(usermsgHeight));
};
/**
* Shows/hides a visual notification, indicating that a message has arrived.
*/
function setVisualNotification(show) {
var unreadMsgElement = document.getElementById('unreadMessages');
if (unreadMessages) {
unreadMsgElement.innerHTML = unreadMessages.toString();
var chatButtonElement
= document.getElementById('chat').parentNode;
var leftIndent = (Util.getTextWidth(chatButtonElement)
- Util.getTextWidth(unreadMsgElement) - 5)/2;
var topIndent = (Util.getTextHeight(chatButtonElement)
- Util.getTextHeight(unreadMsgElement))/2 - 2;
unreadMsgElement.setAttribute(
'style',
'top:' + Util.toInteger(topIndent)
+ '; left:' + Util.toInteger(leftIndent) +';');
}
else
unreadMsgElement.innerHTML = '';
var glower = $('#chat');
if (show && !notificationInterval) {
notificationInterval = window.setInterval(function() {
glower.toggleClass('active');
}, 800);
}
else if (!show && notificationInterval) {
window.clearInterval(notificationInterval);
notificationInterval = false;
glower.removeClass('active');
}
}
/**
* Scrolls chat to the bottom.
*/
function scrollChatToBottom() {
setTimeout(function() {
$('#chatconversation').scrollTop(
$('#chatconversation')[0].scrollHeight);
}, 5);
}
return my;
}(Chat || {}));

View File

@ -3,7 +3,7 @@ html, body{
height:100%; height:100%;
color: #424242; color: #424242;
font-family:'Helvetica Neue', Helvetica, sans-serif; font-family:'Helvetica Neue', Helvetica, sans-serif;
font-weight: 400; font-weight: 400;
background: #e9e9e9; background: #e9e9e9;
overflow-x: hidden; overflow-x: hidden;
} }
@ -185,16 +185,16 @@ html, body{
} }
#left { #left {
display:block; display:block;
position: absolute; position: absolute;
left: 0px; left: 0px;
top: 0px; top: 0px;
width: 100px; width: 100px;
height: 39px; height: 39px;
background-image:url(../images/left1.png); background-image:url(../images/left1.png);
background-repeat:no-repeat; background-repeat:no-repeat;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
#leftlogo { #leftlogo {
@ -222,6 +222,11 @@ html, body{
visibility: hidden; visibility: hidden;
} }
.toolbar_span {
display: inline-block;
position: relative;
}
.button { .button {
display: inline-block; display: inline-block;
position: relative; position: relative;
@ -233,6 +238,30 @@ html, body{
font-size: 11pt; font-size: 11pt;
text-align: center; text-align: center;
text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7); text-shadow: 0px 1px 0px rgba(255,255,255,.3), 0px -1px 0px rgba(0,0,0,.7);
z-index: 1;
}
.toolbar_span>span {
display: inline-block;
position: absolute;
font-size: 7pt;
color: #ffffff;
text-align:center;
cursor: pointer;
}
#chat {
font-size:1.65em;
-webkit-transition: all .5s ease-in-out;;
-moz-transition: all .5s ease-in-out;;
transition: all .5s ease-in-out;;
}
#chat.active {
-webkit-text-shadow: 0 0 10px #ffffff;
-moz-text-shadow: 0 0 10px #ffffff;
text-shadow: 0 0 10px #ffffff;
-webkit-transform: scale(1.1);
} }
a.button:hover { a.button:hover {
@ -266,15 +295,15 @@ a.button:hover {
} }
#right { #right {
display:block; display:block;
position:absolute; position:absolute;
right: 0px; right: 0px;
top: 0px; top: 0px;
background-image:url(../images/right1.png); background-image:url(../images/right1.png);
background-repeat:no-repeat; background-repeat:no-repeat;
margin:0; margin:0;
padding:0; padding:0;
width:100px; width:100px;
height:39px; height:39px;
} }

View File

@ -15,7 +15,8 @@ var Etherpad = (function (my) {
if (!name) { if (!name) {
// In case we're the focus we generate the name. // In case we're the focus we generate the name.
etherpadName = Math.random().toString(36).substring(7) + '_' + (new Date().getTime()).toString(); etherpadName = Math.random().toString(36).substring(7)
+ '_' + (new Date().getTime()).toString();
shareEtherpad(); shareEtherpad();
} }
else else
@ -94,7 +95,8 @@ var Etherpad = (function (my) {
button.appendChild(buttonImage); button.appendChild(buttonImage);
var toolbar = document.getElementById('toolbar'); var toolbar = document.getElementById('toolbar');
toolbar.insertBefore(button, toolbar.childNodes[toolbar.childNodes.length - 4]); toolbar.insertBefore(button,
toolbar.childNodes[toolbar.childNodes.length - 4]);
toolbar.insertBefore(separator, button); toolbar.insertBefore(separator, button);
} }

View File

@ -8,6 +8,8 @@
<script src="muc.js?v=6"></script><!-- simple MUC library --> <script src="muc.js?v=6"></script><!-- simple MUC library -->
<script src="estos_log.js?v=2"></script><!-- simple stanza logger --> <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
<script src="app.js?v=20"></script><!-- application logic --> <script src="app.js?v=20"></script><!-- application logic -->
<script src="chat.js?v=1"></script><!-- chat logic -->
<script src="util.js?v=1"></script><!-- utility functions -->
<script src="etherpad.js?v=2"></script><!-- etherpad plugin --> <script src="etherpad.js?v=2"></script><!-- etherpad plugin -->
<script src="smileys.js?v=1"></script><!-- smiley images --> <script src="smileys.js?v=1"></script><!-- smiley images -->
<script src="replacement.js?v=5"></script><!-- link and smiley replacement --> <script src="replacement.js?v=5"></script><!-- link and smiley replacement -->
@ -25,17 +27,22 @@
<a href="http://jitsi.org" target="_blank"><div id="leftlogo"></div></a> <a href="http://jitsi.org" target="_blank"><div id="leftlogo"></div></a>
<a href="http://www.estos.com/" target="_blank"><div id="rightlogo"></div></a> <a href="http://www.estos.com/" target="_blank"><div id="rightlogo"></div></a>
<span id="toolbar"> <span id="toolbar">
<a class="button" onclick='buttonClick("#mute", "fa fa-microphone fa-lg fa fa-microphone-slash fa-lg");toggleAudio();'><i id="mute" title="Mute / unmute" class="fa fa-microphone fa-lg"></i></a> <a class="button" onclick='buttonClick("#mute", "fa fa-microphone fa-lg fa fa-microphone-slash fa-lg");toggleAudio();'>
<i id="mute" title="Mute / unmute" class="fa fa-microphone fa-lg"></i></a>
<div class="header_button_separator"></div> <div class="header_button_separator"></div>
<a class="button" onclick='buttonClick("#video", "fa fa-video-camera fa-lg fa fa-video-camera no-fa-video-camera fa-lg");toggleVideo();'><i id="video" title="Start / stop camera" class="fa fa-video-camera fa-lg"></i></a> <a class="button" onclick='buttonClick("#video", "fa fa-video-camera fa-lg fa fa-video-camera no-fa-video-camera fa-lg");toggleVideo();'>
<i id="video" title="Start / stop camera" class="fa fa-video-camera fa-lg"></i></a>
<div class="header_button_separator"></div> <div class="header_button_separator"></div>
<a class="button" onclick="openLockDialog();"><i id="lockIcon" title="Lock/unlock room" class="fa fa-unlock fa-lg"></i></a> <a class="button" onclick="openLockDialog();"><i id="lockIcon" title="Lock/unlock room" class="fa fa-unlock fa-lg"></i></a>
<div class="header_button_separator"></div> <div class="header_button_separator"></div>
<a class="button" onclick="openLinkDialog();"><i title="Invite others" class="fa fa-link fa-lg"></i></a> <a class="button" onclick="openLinkDialog();"><i title="Invite others" class="fa fa-link fa-lg"></i></a>
<div class="header_button_separator"></div> <div class="header_button_separator"></div>
<a class="button" onclick='openChat();'><i id="chat" title="Open chat" class="fa fa-comments fa-lg"></i></a> <span class="toolbar_span">
<a class="button" onclick='Chat.toggleChat();'><i id="chat" title="Open chat" class="fa fa-comments-o fa-lg"></i></a>
<span id="unreadMessages"></span>
</span>
<div class="header_button_separator"></div> <div class="header_button_separator"></div>
<a class="button" onclick='openPreziDialog();'><i title="Share prezi" class="fa fa-desktop fa-lg"></i></a> <a class="button" onclick='openPreziDialog();'><i title="Share prezi" class="fa fa-picture-o fa-lg"></i></a>
<div class="header_button_separator"></div> <div class="header_button_separator"></div>
<a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a> <a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a>
</span> </span>
@ -65,6 +72,8 @@
<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> <video id="localVideo" autoplay oncontextmenu="return false;" muted></video>
<span class="focusindicator"></span> <span class="focusindicator"></span>
</span> </span>
<audio id="userJoined" src="sounds/joined.wav" preload="auto"></audio>
<audio id="userLeft" src="sounds/left.wav" preload="auto"></audio>
</div> </div>
</div> </div>
<div id="chatspace"> <div id="chatspace">
@ -74,9 +83,10 @@
<input type='text' id="nickinput" placeholder='Choose a nickname' autofocus> <input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
</form> </form>
</div> </div>
<!--div><i class="fa fa-comments">&nbsp;</i><span class='nick'></span>:&nbsp;<span class='chattext'></span></div--> <!--div><i class="fa fa-comments">&nbsp;</i><span class='nick'></span>:&nbsp;<span class='chattext'></span></div-->
<div id="chatconversation"></div> <div id="chatconversation"></div>
<audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea> <textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
</div> </div>
<a id="downloadlog" onclick='dump(event.target);'><i title="Download support information" class="fa fa-cloud-download"></i></a> <a id="downloadlog" onclick='dump(event.target);'><i title="Download support information" class="fa fa-cloud-download"></i></a>

2
muc.js
View File

@ -143,7 +143,7 @@ Strophe.addConnectionPlugin('emuc', {
if (txt) { if (txt) {
console.log('chat', nick, txt); console.log('chat', nick, txt);
updateChatConversation(nick, txt); Chat.updateChatConversation(nick, txt);
} }
return true; return true;
}, },

BIN
sounds/incomingMessage.wav Normal file

Binary file not shown.

BIN
sounds/joined.wav Normal file

Binary file not shown.

BIN
sounds/left.wav Normal file

Binary file not shown.

43
util.js Normal file
View File

@ -0,0 +1,43 @@
/**
* Utility functions.
*/
var Util = (function (my) {
/**
* Returns the text width for the given element.
*
* @param el the element
*/
my.getTextWidth = function(el) {
return (el.clientWidth + 1);
};
/**
* Returns the text height for the given element.
*
* @param el the element
*/
my.getTextHeight = function(el) {
return (el.clientHeight + 1);
};
/**
* Casts the given number to integer.
*
* @param number the number to cast
*/
my.toInteger = function(number) {
return Math.round(Number(number));
};
/**
* Plays the sound given by id.
*
* @param id the identifier of the audio element.
*/
my.playSoundNotification = function(id) {
document.getElementById(id).play();
};
return my;
}(Util || {}));