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.className = 'videocontainer';
remotes.appendChild(container);
console.log("PLAY USER JOINEDDDDDDDD");
Util.playSoundNotification('userJoined');
}
var vid = document.createElement('video');
var id = 'remoteVideo_' + sid + '_' + data.stream.id;
@ -212,11 +214,12 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
if (pick) {
if (pick.src === localVideoSrc)
isLocalVideo = true;
updateLargeVideo(pick.src, isLocalVideo, pick.volume);
}
}
$('#' + id).parent().remove();
Util.playSoundNotification('userLeft');
resizeThumbnails();
};
sel.click(
@ -632,8 +635,8 @@ function toggleAudio() {
}
}
function resizeLarge() {
resizeChat();
var resizeLarge = function () {
Chat.resizeChat();
var availableHeight = window.innerHeight;
var chatspaceWidth = $('#chatspace').is(":visible")
? $('#chatspace').width()
@ -666,7 +669,7 @@ function resizeLarge() {
}
resizeThumbnails();
}
};
function resizeThumbnails() {
// 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);
}
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 () {
var storedDisplayName = window.localStorage.displayname;
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});
Chat.init();
// Set the defaults for prompt dialogs.
jQuery.prompt.setDefaults({persistent: false});
@ -826,24 +767,6 @@ function dump(elem, filename){
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.
*/
@ -1092,44 +1015,6 @@ function updateLockButton() {
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.
*/
@ -1199,7 +1084,7 @@ function addRemoteVideoContainer(id) {
return container;
}
/*
/**
* Creates the element indicating the focus of the conference.
*/
function createFocusIndicatorElement(parentElement) {
@ -1209,13 +1094,7 @@ function createFocusIndicatorElement(parentElement) {
parentElement.appendChild(focusIndicator);
}
function scrollChatToBottom() {
setTimeout(function() {
$('#chatconversation').scrollTop($('#chatconversation')[0].scrollHeight);
}, 5);
}
/*
/**
* Toggles the application in and out of full screen mode
* (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) {
var nameSpan = $('#' + videoSpanId + '>span.displayname');
@ -1306,7 +1185,7 @@ function showDisplayName(videoSpanId, displayName) {
connection.emuc.addDisplayNameToPresence(nickname);
connection.emuc.sendPresence();
setChatConversationMode(true);
Chat.setChatConversationMode(true);
}
if (!$('#localDisplayName').is(":visible")) {
@ -1337,13 +1216,4 @@ function createEditDisplayNameButton() {
editButton.innerHTML = '<i class="fa fa-pencil"></i>';
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%;
color: #424242;
font-family:'Helvetica Neue', Helvetica, sans-serif;
font-weight: 400;
font-weight: 400;
background: #e9e9e9;
overflow-x: hidden;
}
@ -185,16 +185,16 @@ html, body{
}
#left {
display:block;
display:block;
position: absolute;
left: 0px;
left: 0px;
top: 0px;
width: 100px;
height: 39px;
background-image:url(../images/left1.png);
background-repeat:no-repeat;
margin: 0;
padding: 0;
background-image:url(../images/left1.png);
background-repeat:no-repeat;
margin: 0;
padding: 0;
}
#leftlogo {
@ -222,6 +222,11 @@ html, body{
visibility: hidden;
}
.toolbar_span {
display: inline-block;
position: relative;
}
.button {
display: inline-block;
position: relative;
@ -233,6 +238,30 @@ html, body{
font-size: 11pt;
text-align: center;
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 {
@ -266,15 +295,15 @@ a.button:hover {
}
#right {
display:block;
display:block;
position:absolute;
right: 0px;
top: 0px;
background-image:url(../images/right1.png);
background-repeat:no-repeat;
margin:0;
padding:0;
width:100px;
background-image:url(../images/right1.png);
background-repeat:no-repeat;
margin:0;
padding:0;
width:100px;
height:39px;
}

View File

@ -15,7 +15,8 @@ var Etherpad = (function (my) {
if (!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();
}
else
@ -94,7 +95,8 @@ var Etherpad = (function (my) {
button.appendChild(buttonImage);
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);
}

View File

@ -8,6 +8,8 @@
<script src="muc.js?v=6"></script><!-- simple MUC library -->
<script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
<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="smileys.js?v=1"></script><!-- smiley images -->
<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://www.estos.com/" target="_blank"><div id="rightlogo"></div></a>
<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>
<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>
<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>
<a class="button" onclick="openLinkDialog();"><i title="Invite others" class="fa fa-link fa-lg"></i></a>
<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>
<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>
<a class="button" onclick='toggleFullScreen();'><i title="Enter / Exit Full Screen" class="fa fa-arrows-alt fa-lg"></i></a>
</span>
@ -65,6 +72,8 @@
<video id="localVideo" autoplay oncontextmenu="return false;" muted></video>
<span class="focusindicator"></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 id="chatspace">
@ -74,9 +83,10 @@
<input type='text' id="nickinput" placeholder='Choose a nickname' autofocus>
</form>
</div>
<!--div><i class="fa fa-comments">&nbsp;</i><span class='nick'></span>:&nbsp;<span class='chattext'></span></div-->
<div id="chatconversation"></div>
<audio id="chatNotification" src="sounds/incomingMessage.wav" preload="auto"></audio>
<textarea id="usermsg" placeholder='Enter text...' autofocus></textarea>
</div>
<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) {
console.log('chat', nick, txt);
updateChatConversation(nick, txt);
Chat.updateChatConversation(nick, txt);
}
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 || {}));