From 1d4177faebfcce0b71790641f4faaca7c0e75e18 Mon Sep 17 00:00:00 2001 From: fo Date: Fri, 31 Oct 2014 13:47:12 +0200 Subject: [PATCH] Adds a side panel toggler, settings menu, avatars, uuids. --- app.js | 64 ++++++++-- avatar.js | 138 ++++++++++++++++++++ bottom_toolbar.js | 24 +--- chat.js | 142 ++------------------- contact_list.js | 194 ++++++++-------------------- css/contact_list.css | 1 + css/font.css | 4 + css/main.css | 7 +- css/settingsmenu.css | 45 +++++++ css/videolayout_default.css | 24 +++- fonts/jitsi.eot | Bin 11912 -> 12856 bytes fonts/jitsi.svg | 1 + fonts/jitsi.ttf | Bin 11756 -> 12700 bytes fonts/jitsi.woff | Bin 11832 -> 12776 bytes fonts/selection.json | 49 ++++++-- index.html | 49 +++++--- interface_config.js | 4 +- media_stream.js | 10 +- muc.js | 14 +++ settings_menu.js | 83 ++++++++++++ side_panel_toggler.js | 245 ++++++++++++++++++++++++++++++++++++ util.js | 8 +- videolayout.js | 85 +++++++------ 23 files changed, 803 insertions(+), 388 deletions(-) create mode 100644 avatar.js create mode 100644 css/settingsmenu.css create mode 100644 settings_menu.js create mode 100644 side_panel_toggler.js diff --git a/app.js b/app.js index 928c90436..7d7780217 100644 --- a/app.js +++ b/app.js @@ -11,7 +11,7 @@ var recordingToken =''; var roomUrl = null; var roomName = null; var ssrc2jid = {}; -var mediaStreams = []; +var mediaStreams = {}; var bridgeIsDown = false; /** @@ -99,9 +99,16 @@ function connect(jid, password) { localVideo = connection.jingle.localVideo; } connection = new Strophe.Connection(document.getElementById('boshURL').value || config.bosh || '/http-bind'); - - if (nickname) { - connection.emuc.addDisplayNameToPresence(nickname); + + var email = SettingsMenu.getEmail(); + var displayName = SettingsMenu.getDisplayName(); + if(email) { + connection.emuc.addEmailToPresence(email); + } else { + connection.emuc.addUserIdToPresence(SettingsMenu.getUID()); + } + if(displayName) { + connection.emuc.addDisplayNameToPresence(displayName); } if (connection.disco) { @@ -335,7 +342,12 @@ function waitForPresence(data, sid) { // NOTE(gp) now that we have simulcast, a media stream can have more than 1 // ssrc. We should probably take that into account in our MediaStream // wrapper. - mediaStreams.push(new MediaStream(data, sid, thessrc)); + var mediaStream = new MediaStream(data, sid, thessrc); + var jid = data.peerjid || connection.emuc.myroomjid; + if(!mediaStreams[jid]) { + mediaStreams[jid] = {}; + } + mediaStreams[jid][mediaStream.type] = mediaStream; var container; var remotes = document.getElementById('remoteVideos'); @@ -369,6 +381,8 @@ function waitForPresence(data, sid) { data.stream, data.peerjid, thessrc); + if(isVideo && container.id !== 'mixedstream') + videoSrcToSsrc[$(container).find('>video')[0].src] = thessrc; } // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 @@ -699,7 +713,7 @@ $(document).bind('joined.muc', function (event, jid, info) { VideoLayout.showFocusIndicator(); // Add myself to the contact list. - ContactList.addContact(jid); + ContactList.addContact(jid, SettingsMenu.getEmail() || SettingsMenu.getUID()); // Once we've joined the muc show the toolbar ToolbarToggler.showToolbar(); @@ -721,7 +735,12 @@ $(document).bind('entered.muc', function (event, jid, info, pres) { console.log('is focus? ' + (focus ? 'true' : 'false')); // Add Peer's container - VideoLayout.ensurePeerContainerExists(jid); + var id = $(pres).find('>userID').text(); + var email = $(pres).find('>email'); + if(email.length > 0) { + id = email.text(); + } + VideoLayout.ensurePeerContainerExists(jid,id); if(APIConnector.isEnabled() && APIConnector.isEventEnabled("participantJoined")) { @@ -879,6 +898,13 @@ $(document).bind('presence.muc', function (event, jid, info, pres) { "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(); + } + Avatar.setUserAvatar(jid, id); + }); $(document).bind('presence.status.muc', function (event, jid, info, pres) { @@ -1358,15 +1384,20 @@ $(document).ready(function () { "showMethod": "fadeIn", "hideMethod": "fadeOut", "reposition": function() { - if(Chat.isVisible() || ContactList.isVisible()) { - $("#toast-container").addClass("toast-bottom-right-center"); + if(PanelToggler.isVisible()) { + $("#toast-container").addClass("notification-bottom-right-center"); } else { - $("#toast-container").removeClass("toast-bottom-right-center"); + $("#toast-container").removeClass("notification-bottom-right-center"); } }, "newestOnTop": false - } + }; + $('#settingsmenu>input').keyup(function(event){ + if(event.keyCode === 13) {//enter + SettingsMenu.update(); + } + }) }); @@ -1661,3 +1692,14 @@ function hangup() { ); } + +$(document).on('videomuted.muc', function(event, jid, value) { + if(mediaStreams[jid] && mediaStreams[jid][MediaStream.VIDEO_TYPE]) { + var stream = mediaStreams[jid][MediaStream.VIDEO_TYPE]; + var isMuted = (value === "true"); + if (isMuted != stream.muted) { + stream.muted = isMuted; + Avatar.showUserAvatar(jid, isMuted); + } + } +}); diff --git a/avatar.js b/avatar.js new file mode 100644 index 000000000..f1bbf1734 --- /dev/null +++ b/avatar.js @@ -0,0 +1,138 @@ +var Avatar = (function(my) { + var users = {}; + var activeSpeakerJid; + /** + * Sets the user's avatar in the settings menu(if local user), contact list + * and thumbnail + * @param jid jid of the user + * @param id email or userID to be used as a hash + */ + my.setUserAvatar = function(jid, id) { + if(id) { + if(users[jid] === id) { + return; + } + users[jid] = id; + } + var url = getGravatarUrl(users[jid]); + var resourceJid = Strophe.getResourceFromJid(jid); + var thumbnail = $('#participant_' + resourceJid); + var avatar = $('#avatar_' + resourceJid); + + // set the avatar in the settings menu if it is local user and get the + // local video container + if(jid === connection.emuc.myroomjid) { + $('#avatar').get(0).src = url; + thumbnail = $('#localVideoContainer'); + } + + // set the avatar in the contact list + var contact = $('#' + resourceJid + '>img'); + if(contact && contact.length > 0) { + contact.get(0).src = url; + } + + // set the avatar in the thumbnail + if(avatar && avatar.length > 0) { + avatar[0].src = url; + } else { + if (thumbnail && thumbnail.length > 0) { + avatar = document.createElement('img'); + avatar.id = 'avatar_' + resourceJid; + avatar.className = 'userAvatar'; + avatar.src = url; + thumbnail.append(avatar); + } + } + + //if the user is the current active speaker - update the active speaker + // avatar + if(jid === activeSpeakerJid) { + Avatar.updateActiveSpeakerAvatarSrc(jid); + } + }; + + /** + * Hides or shows the user's avatar + * @param jid jid of the user + * @param show whether we should show the avatar or not + * video because there is no dominant speaker and no focused speaker + */ + my.showUserAvatar = function(jid, show) { + if(users[jid]) { + var resourceJid = Strophe.getResourceFromJid(jid); + var video = $('#participant_' + resourceJid + '>video'); + var avatar = $('#avatar_' + resourceJid); + + if(jid === connection.emuc.myroomjid) { + video = $('#localVideoWrapper>video'); + } + if(show === undefined || show === null) { + show = isUserMuted(jid); + } + + //if the user is the currently focused, the dominant speaker or if + //there is no focused and no dominant speaker + if (activeSpeakerJid === jid) { + setVisibility($("#largeVideo"), !show); + setVisibility($('#activeSpeakerAvatar'), show); + setVisibility(avatar, false); + setVisibility(video, false); + } else { + if (video && video.length > 0) { + setVisibility(video, !show); + setVisibility(avatar, show); + } + } + } + }; + + /** + * Updates the src of the active speaker avatar + * @param jid of the current active speaker + */ + my.updateActiveSpeakerAvatarSrc = function(jid) { + if(!jid) { + if (focusedVideoSrc) { + jid = getJidFromVideoSrc(focusedVideoSrc); + } else { + jid = connection.emuc.findJidFromResource( + VideoLayout.getDominantSpeakerResourceJid()); + } + } + var avatar = $("#activeSpeakerAvatar")[0]; + var url = getGravatarUrl(users[jid], + interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE); + if(jid === activeSpeakerJid && avatar.src === url) { + return; + } + activeSpeakerJid = jid; + var isMuted = isUserMuted(jid); + if(jid && isMuted !== null) { + avatar.src = url; + setVisibility($("#largeVideo"), !isMuted); + Avatar.showUserAvatar(jid, isMuted); + } + }; + + function setVisibility(selector, show) { + if (selector && selector.length > 0) { + selector.css("visibility", show ? "visible" : "hidden"); + } + } + + function isUserMuted(jid) { + if(!mediaStreams[jid] || !mediaStreams[jid][MediaStream.VIDEO_TYPE]) { + return null; + } + return mediaStreams[jid][MediaStream.VIDEO_TYPE].muted; + } + + function getGravatarUrl(email, size) { + return 'https://www.gravatar.com/avatar/' + + (email ? MD5.hexdigest(email.trim().toLowerCase()) : SettingsMenu.getUID()) + + "?d=retro&size=" + (size || "30"); + } + + return my; +}(Avatar || {})); diff --git a/bottom_toolbar.js b/bottom_toolbar.js index c504c944d..13073632e 100644 --- a/bottom_toolbar.js +++ b/bottom_toolbar.js @@ -1,30 +1,10 @@ var BottomToolbar = (function (my) { my.toggleChat = function() { - if (ContactList.isVisible()) { - buttonClick("#contactListButton", "active"); - $('#contactlist').css('z-index', 4); - setTimeout(function() { - $('#contactlist').css('display', 'none'); - $('#contactlist').css('z-index', 5); - }, 500); - } - - Chat.toggleChat(); - - buttonClick("#chatBottomButton", "active"); + PanelToggler.toggleChat(); }; my.toggleContactList = function() { - if (Chat.isVisible()) { - buttonClick("#chatBottomButton", "active"); - setTimeout(function() { - $('#chatspace').css('display', 'none'); - }, 500); - } - - buttonClick("#contactListButton", "active"); - - ContactList.toggleContactList(); + PanelToggler.toggleContactList(); }; my.toggleFilmStrip = function() { diff --git a/chat.js b/chat.js index e90635042..2dee6c514 100644 --- a/chat.js +++ b/chat.js @@ -57,7 +57,7 @@ var Chat = (function (my) { var onTextAreaResize = function () { resizeChatConversation(); - scrollChatToBottom(); + Chat.scrollChatToBottom(); }; $('#usermsg').autosize({callback: onTextAreaResize}); @@ -144,112 +144,7 @@ var Chat = (function (my) { } }; - /** - * Opens / closes the chat area. - */ - my.toggleChat = function () { - var chatspace = $('#chatspace'); - var videospace = $('#videospace'); - var chatSize = (Chat.isVisible()) ? [0, 0] : Chat.getChatSize(); - var videospaceWidth = window.innerWidth - chatSize[0]; - var videospaceHeight = window.innerHeight; - var videoSize - = getVideoSize(null, null, videospaceWidth, videospaceHeight); - var videoWidth = videoSize[0]; - var videoHeight = videoSize[1]; - var videoPosition = getVideoPosition(videoWidth, - videoHeight, - videospaceWidth, - videospaceHeight); - var horizontalIndent = videoPosition[0]; - var verticalIndent = videoPosition[1]; - - var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth); - var thumbnailsWidth = thumbnailSize[0]; - var thumbnailsHeight = thumbnailSize[1]; - var completeFunction = Chat.isVisible() ? - function() {} : function () { - scrollChatToBottom(); - chatspace.trigger('shown'); - }; - - videospace.animate({right: chatSize[0], - width: videospaceWidth, - height: videospaceHeight}, - {queue: false, - duration: 500, - complete: completeFunction}); - - $('#remoteVideos').animate({height: thumbnailsHeight}, - {queue: false, - duration: 500}); - - $('#remoteVideos>span').animate({height: thumbnailsHeight, - width: thumbnailsWidth}, - {queue: false, - duration: 500, - complete: function() { - $(document).trigger( - "remotevideo.resized", - [thumbnailsWidth, - thumbnailsHeight]); - }}); - - $('#largeVideoContainer').animate({ width: videospaceWidth, - height: videospaceHeight}, - {queue: false, - duration: 500 - }); - - $('#largeVideo').animate({ width: videoWidth, - height: videoHeight, - top: verticalIndent, - bottom: verticalIndent, - left: horizontalIndent, - right: horizontalIndent}, - { queue: false, - duration: 500 - } - ); - - if (Chat.isVisible()) { - $("#toast-container").animate({right: '5px'}, - {queue: false, - duration: 500}); - chatspace.hide("slide", { direction: "right", - queue: false, - duration: 500}); - - } - else { - // Undock the toolbar when the chat is shown and if we're in a - // video mode. - if (VideoLayout.isLargeVideoVisible()) { - ToolbarToggler.dockToolbar(false); - } - - - $("#toast-container").animate({right: (chatSize[0] + 5) + 'px'}, - {queue: false, - duration: 500}); - chatspace.show("slide", { direction: "right", - queue: false, - duration: 500, - complete: function () { - // Request the focus in the nickname field or the chat input field. - if ($('#nickname').css('visibility') === 'visible') { - $('#nickinput').focus(); - } else { - $('#usermsg').focus(); - } - } - }); - - Chat.resizeChat(); - } - - }; /** * Sets the chat conversation mode. @@ -268,7 +163,7 @@ var Chat = (function (my) { * Resizes the chat area. */ my.resizeChat = function () { - var chatSize = Chat.getChatSize(); + var chatSize = PanelToggler.getPanelSize(); $('#chatspace').width(chatSize[0]); $('#chatspace').height(chatSize[1]); @@ -276,20 +171,6 @@ var Chat = (function (my) { resizeChatConversation(); }; - /** - * Returns the size of the chat. - */ - my.getChatSize = function () { - var availableHeight = window.innerHeight; - var availableWidth = window.innerWidth; - - var chatWidth = 200; - if (availableWidth * 0.2 < 200) - chatWidth = availableWidth * 0.2; - - return [chatWidth, availableHeight]; - }; - /** * Indicates if the chat is currently visible. */ @@ -309,6 +190,16 @@ var Chat = (function (my) { $('#usermsg').focus(); }; + /** + * Scrolls chat to the bottom. + */ + my.scrollChatToBottom = function() { + setTimeout(function () { + $('#chatconversation').scrollTop( + $('#chatconversation')[0].scrollHeight); + }, 5); + }; + /** * Adds the smileys container to the chat */ @@ -426,15 +317,6 @@ var Chat = (function (my) { } } - /** - * Scrolls chat to the bottom. - */ - function scrollChatToBottom() { - setTimeout(function () { - $('#chatconversation').scrollTop( - $('#chatconversation')[0].scrollHeight); - }, 5); - } /** * Returns the current time in the format it is shown to the user diff --git a/contact_list.js b/contact_list.js index 4e31d1be7..68536ff72 100644 --- a/contact_list.js +++ b/contact_list.js @@ -20,22 +20,24 @@ var ContactList = (function (my) { * Adds a contact for the given peerJid if such doesn't yet exist. * * @param peerJid the peerJid corresponding to the contact + * @param id the user's email or userId used to get the user's avatar */ - my.ensureAddContact = function(peerJid) { + my.ensureAddContact = function(peerJid, id) { var resourceJid = Strophe.getResourceFromJid(peerJid); var contact = $('#contactlist>ul>li[id="' + resourceJid + '"]'); if (!contact || contact.length <= 0) - ContactList.addContact(peerJid); + ContactList.addContact(peerJid,id); }; /** * Adds a contact for the given peer jid. * * @param peerJid the jid of the contact to add + * @param id the email or userId of the user */ - my.addContact = function(peerJid) { + my.addContact = function(peerJid, id) { var resourceJid = Strophe.getResourceFromJid(peerJid); var contactlist = $('#contactlist>ul'); @@ -51,7 +53,7 @@ var ContactList = (function (my) { } }; - newContact.appendChild(createAvatar()); + newContact.appendChild(createAvatar(id)); newContact.appendChild(createDisplayNameParagraph("Participant")); var clElement = contactlist.get(0); @@ -87,145 +89,7 @@ var ContactList = (function (my) { } }; - /** - * Opens / closes the contact list area. - */ - my.toggleContactList = function () { - var contactlist = $('#contactlist'); - var videospace = $('#videospace'); - - var chatSize = (ContactList.isVisible()) ? [0, 0] : Chat.getChatSize(); - var videospaceWidth = window.innerWidth - chatSize[0]; - var videospaceHeight = window.innerHeight; - var videoSize - = getVideoSize(null, null, videospaceWidth, videospaceHeight); - var videoWidth = videoSize[0]; - var videoHeight = videoSize[1]; - var videoPosition = getVideoPosition(videoWidth, - videoHeight, - videospaceWidth, - videospaceHeight); - var horizontalIndent = videoPosition[0]; - var verticalIndent = videoPosition[1]; - - var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth); - var thumbnailsWidth = thumbnailSize[0]; - var thumbnailsHeight = thumbnailSize[1]; - var completeFunction = ContactList.isVisible() ? - function() {} : function () { contactlist.trigger('shown');}; - - videospace.animate({right: chatSize[0], - width: videospaceWidth, - height: videospaceHeight}, - {queue: false, - duration: 500, - complete: completeFunction - }); - - $('#remoteVideos').animate({height: thumbnailsHeight}, - {queue: false, - duration: 500}); - - $('#remoteVideos>span').animate({height: thumbnailsHeight, - width: thumbnailsWidth}, - {queue: false, - duration: 500, - complete: function() { - $(document).trigger( - "remotevideo.resized", - [thumbnailsWidth, - thumbnailsHeight]); - }}); - - $('#largeVideoContainer').animate({ width: videospaceWidth, - height: videospaceHeight}, - {queue: false, - duration: 500 - }); - - $('#largeVideo').animate({ width: videoWidth, - height: videoHeight, - top: verticalIndent, - bottom: verticalIndent, - left: horizontalIndent, - right: horizontalIndent}, - { queue: false, - duration: 500 - }); - - if (ContactList.isVisible()) { - $("#toast-container").animate({right: '12px'}, - {queue: false, - duration: 500}); - $('#contactlist').hide("slide", { direction: "right", - queue: false, - duration: 500}); - } else { - // Undock the toolbar when the chat is shown and if we're in a - // video mode. - if (VideoLayout.isLargeVideoVisible()) - ToolbarToggler.dockToolbar(false); - - - $("#toast-container").animate({right: '212px'}, - {queue: false, - duration: 500}); - $('#contactlist').show("slide", { direction: "right", - queue: false, - duration: 500}); - - //stop the glowing of the contact list icon - setVisualNotification(false); - } - }; - - /** - * Updates the number of participants in the contact list button and sets - * the glow - * @param delta indicates whether a new user has joined (1) or someone has - * left(-1) - */ - function updateNumberOfParticipants(delta) { - //when the user is alone we don't show the number of participants - if(numberOfContacts === 0) { - $("#numberOfParticipants").text(''); - numberOfContacts += delta; - } else if(numberOfContacts !== 0 && !ContactList.isVisible()) { - setVisualNotification(true); - numberOfContacts += delta; - $("#numberOfParticipants").text(numberOfContacts); - } - }; - - /** - * Creates the avatar element. - * - * @return the newly created avatar element - */ - function createAvatar() { - var avatar = document.createElement('i'); - avatar.className = "icon-avatar avatar"; - - return avatar; - } - - /** - * Creates the display name paragraph. - * - * @param displayName the display name to set - */ - function createDisplayNameParagraph(displayName) { - var p = document.createElement('p'); - p.innerText = displayName; - - return p; - } - - /** - * Shows/hides a visual notification, indicating that a new user has joined - * the conference. - */ - function setVisualNotification(show, stopGlowingIn) { + my.setVisualNotification = function(show, stopGlowingIn) { var glower = $('#contactListButton'); function stopGlowing() { window.clearInterval(notificationInterval); @@ -247,8 +111,52 @@ var ContactList = (function (my) { if(stopGlowingIn) { setTimeout(stopGlowing, stopGlowingIn); } + }; + + /** + * Updates the number of participants in the contact list button and sets + * the glow + * @param delta indicates whether a new user has joined (1) or someone has + * left(-1) + */ + function updateNumberOfParticipants(delta) { + //when the user is alone we don't show the number of participants + if(numberOfContacts === 0) { + $("#numberOfParticipants").text(''); + numberOfContacts += delta; + } else if(numberOfContacts !== 0 && !ContactList.isVisible()) { + ContactList.setVisualNotification(true); + numberOfContacts += delta; + $("#numberOfParticipants").text(numberOfContacts); + } } + /** + * Creates the avatar element. + * + * @return the newly created avatar element + */ + function createAvatar(id) { + var avatar = document.createElement('img'); + avatar.className = "icon-avatar avatar"; + avatar.src = "https://www.gravatar.com/avatar/" + id + "?d=retro&size=30"; + + return avatar; + } + + /** + * Creates the display name paragraph. + * + * @param displayName the display name to set + */ + function createDisplayNameParagraph(displayName) { + var p = document.createElement('p'); + p.innerText = displayName; + + return p; + } + + /** * Indicates that the display name has changed. */ diff --git a/css/contact_list.css b/css/contact_list.css index 335f26e8a..07a682162 100644 --- a/css/contact_list.css +++ b/css/contact_list.css @@ -34,6 +34,7 @@ margin-right: 10px; vertical-align: middle; font-size: 22pt; + border-radius: 20px; } #contactlist .clickable { diff --git a/css/font.css b/css/font.css index e08b8705d..dbd44a2f7 100644 --- a/css/font.css +++ b/css/font.css @@ -112,4 +112,8 @@ .icon-connection:before { line-height: normal; content: "\e61a"; +} + +.icon-settings:before { + content: "\e61b"; } \ No newline at end of file diff --git a/css/main.css b/css/main.css index 9a0a6e54e..4f55b58e8 100644 --- a/css/main.css +++ b/css/main.css @@ -13,8 +13,7 @@ html, body{ overflow-x: hidden; } -#chatspace, -#contactlist { +.right-panel { display:none; position:absolute; float: right; @@ -38,10 +37,6 @@ html, body{ display:none; } -#settingsButton { - visibility: hidden; -} - .toolbar_span { display: inline-block; position: relative; diff --git a/css/settingsmenu.css b/css/settingsmenu.css new file mode 100644 index 000000000..46d68f69e --- /dev/null +++ b/css/settingsmenu.css @@ -0,0 +1,45 @@ +#settingsmenu { + background: black; + color: #00ccff; +} + +#settingsmenu input { + margin-top: 10px; + margin-left: 10%; + width: 80%; + font-size: 14px; + background: #3a3a3a; + border: none; + box-shadow: none; + color: #a7a7a7; +} + +#settingsmenu .arrow-up { + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-bottom: 5px solid #3a3a3a; + position: relative; + top: 10px; + margin-left: auto; + margin-right: auto; +} + +#settingsmenu button { + width: 36%; + left: 32%; + padding: 0; + margin-top: 10px; +} + +#settingsmenu #avatar { + width: 24%; + left: 38%; + border-radius: 25px; + position: relative; +} + +#settingsmenu .icon-settings { + padding: 34px; +} diff --git a/css/videolayout_default.css b/css/videolayout_default.css index 1bfddccf2..401c6f69d 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -35,7 +35,9 @@ #remoteVideos .videocontainer { display: inline-block; - background-image:url(../images/avatar1.png); + background-color: black; + background-repeat: no-repeat; + background-position: 45; background-size: contain; border-radius:8px; border: 2px solid #212425; @@ -115,10 +117,6 @@ height: 100%; } -.dominantspeaker { - background: #000 !important; -} - #etherpad, #presentation { text-align: center; @@ -378,3 +376,19 @@ #mixedstream { display:none !important; } + +#activeSpeakerAvatar { + visibility: hidden; + width: 100px; + height: 100px; + margin: auto; + position: relative; + border-radius: 50px; +} + +.userAvatar { + height: 100%; + position: absolute; + left: 35px; + border-radius: 200px; +} diff --git a/fonts/jitsi.eot b/fonts/jitsi.eot index eaed7ee86cd0a0392a826698b06553b53651ffe0..d85113241b27b12cfae8227f8d04c4fcfb233e0a 100644 GIT binary patch delta 1187 zcmZWoJ8v6D5Z<|c-!1P!-5seTMV@&M6;P5NdXQFR2t@oq02fAzATfdA_VpcS^8zTJ=o%Z z{CbU$vkH0LAtv-Te~57(|QbT_=={zlJ{4#*g3<)Smna&5nWperMO| z3%EeYN8^Je9u=ZJe+K5;tG!M55SX7}t=;W)SEt|8U+Kd!5+5pYT1mx8!iQ4bVe~XiHnHa@yz@T6_iTqrJh&u(uR`K!GvJhS}j}4geQa%R5GM+4ir~p zC{?7q1oIuVY`6gSn9$x5w$ zmKB_03QwM4Jx)ur27#YA9G0A0uh)Au>B-2N7RhsrCGKxbXea|2SXlK|cO!@g8HJ6Bxf+?3?s8uG+mhtUswQ?ZA6EH-`ZL delta 249 zcmdmy(h + \ No newline at end of file diff --git a/fonts/jitsi.ttf b/fonts/jitsi.ttf index 90d00844c5b4d433d9f3c5476888d6791aa2893c..e3d1af5c636032e6124aae5088057d10396e3ca7 100644 GIT binary patch delta 1204 zcmZWo&u<$=6rS1ny*u7rJG*OV?bxmt9Avjm$Pc?AD6(iNNFY@RA%r-9m8SI}p%Lw& zNJY*8IB}u5Ai;tEp%(<<$N{NB@IP>aD;F;Cjh#eM8GW;}@6DU{zW0shulK**of1k2 z3CIy)WcAkDD{J9n_9{Flm>=#Qj1Tj7o_?)d&8))@8#tG>Jc$sPOC?|;CC z|H{{sz42}F>o;!@vZAp+n80E7#OJU_u%DY8Jb3tNNPmER8}`xuz1^+xmp6|#;6M`c z%E9>IA-O`g30ZvscKf68!QPMiEC0ZL9Yxj;@7;d@q8Kke`s(}FX#Fw0PU}x@&{ogV ze}Cq;*a^Kx$n(cEO{RtHE?=d~-HqW6eHQS~u-P392OIPM`B#PX9BD|aNmk9|#eip1 z4c4kn>X0Sp2c0=}t~D1u$W9|J@)*p>U8Tc~!NrXK&y|9~X@Oa`d6H9)JTEz?SadsQ zo+(u_*__9nMxPO!3p3Zi3xm`Ll5VoFyuc)5iUnB^RD-OkmGa<_Jm0M6o4E|6w2~=~ z5FseTj*68o>mWtEO^PVW10g*OO1-H^wUU-g!OCkwrbOQrbY`XVqzgTgxYP>gHNjZ}yWl?u_Z;{oaY zMhuENHnwH~gX+FevDOux^;1-7Z#Nq2HStnWjC?8MOJyfmpyrB+_X@LM07@zIIF(K< z!Kf9GsH{;WTw?c1$+Q&`to#n`Hn6I8#|$fczOizDh=J zNkzq8#+^Vu$o!g|{AA0-(y9g%1_m7qpnyYeVnqRi4x=fMUjgJR^$_ zU^FhsFD?N(6bN+BOx)qlC^d=EnNe!93!}W2B2b=zp>NKIfARb_Um3VrK&lunb_W51Vscv!n!sOySCVZ z6_oTred>cHJ_!2YpCHr+(fTMTEK>gm9}0c;MFc%JNtdNw&Nnmn%(>t9-E*1w;q6C< z>4yjV`wnq1EET+u**#{Em%J z0`lYW^+V`}8yg^AvT=9+&eqB3HVSXt!n`$$3uO2G(a90GFHwBc#!tr=|GshkCN!6R zz4y-nsZFnvE2MV+3)1Ww^6xL~fIcA4InJ}w7n69Bi_YpgS?z2MhU960pJBVxAN058 z&-1Gc$QhD=RN}Ofit~b}Q}r)b8pI-v&lcM0#8Q{7x}Po-=lkf-np-D>t^WCp|Id}6 zgGGT^wt12TBhE_BTg*G{C(MLM5pU0R3-yx*=f=!Ea05T_fVdNvSIbmTDyg6Pe#K84 zN=g@mVA*yp+s=e9gb`FSNH`}HZ)7A@QTYkk_GU>JH9(l_aw-GKG~JeI0kv3HWXjJ#OjFc*=w3nbtYqwtCtn%mce9sdiS}9t? zd^vZFzm@B<1_&Y4WkguFG^2`ov7R98i4~Ne8XQlh7ss7qQ1x^`DUL`C-lNH)~S(bAO3MbD{kJG}E0q}^;q2%&Ty*{W(Uly%tMY%y# zzMr>WZeyePc=1gmy5IgbekXk4FUMyf9OvQZJ^Whc_vyplfV8G+?PJDX!*A--wLO!b P;-y{NB0Kj_*N(}5W(1A* delta 303 zcmaEnydy@e+~3WOfsp|S^eh;-LA3502FA%34aFwv2-mwL=Oz{~Ffe8S#eG0nD)dZY zdSWq1YzvUj0mTC8IhAQZu{{h7njb*;8~@FD8L5dW3=G<9fNIP@m~~EHV+K$VDE0)% zR{>$pRMwV^+>#0g1|5)DH6UEEmvLuKelk#l^{{6~I`B z(KIhHHx(!bG?394gm>rY|0>8YE&=*SV&WfvMybg@jLwWwn - - + + - + - - - + + + + @@ -47,33 +48,36 @@ - + - - + + - - + + + - - + + + - + + @@ -227,6 +231,10 @@
+ + + +
@@ -253,6 +261,7 @@
powered by jitsi.org +
@@ -296,7 +305,7 @@
-
+
Enter a nickname in the box below
@@ -314,11 +323,19 @@
-
+
  • CONTACT LIST
+
+
SETTINGS
+ +
+ + + +
diff --git a/interface_config.js b/interface_config.js index b8e836033..a9c20b65a 100644 --- a/interface_config.js +++ b/interface_config.js @@ -6,11 +6,13 @@ var interfaceConfig = { TOOLBAR_TIMEOUT: 4000, DEFAULT_REMOTE_DISPLAY_NAME: "Fellow Jitster", DEFAULT_DOMINANT_SPEAKER_DISPLAY_NAME: "Speaker", + DEFAULT_LOCAL_DISPLAY_NAME: "me", SHOW_JITSI_WATERMARK: true, JITSI_WATERMARK_LINK: "http://jitsi.org", SHOW_BRAND_WATERMARK: false, BRAND_WATERMARK_LINK: "", SHOW_POWERED_BY: false, GENERATE_ROOMNAMES_ON_WELCOME_PAGE: true, - APP_NAME: "Jitsi Meet" + APP_NAME: "Jitsi Meet", + ACTIVE_SPEAKER_AVATAR_SIZE: 100 }; diff --git a/media_stream.js b/media_stream.js index 7a71f6aba..348a258e5 100644 --- a/media_stream.js +++ b/media_stream.js @@ -16,15 +16,17 @@ var MediaStream = (function() { * @constructor */ function MediaStreamProto(data, sid, ssrc) { - this.VIDEO_TYPE = "Video"; - this.AUDIO_TYPE = "Audio"; this.stream = data.stream; this.peerjid = data.peerjid; this.ssrc = ssrc; this.session = connection.jingle.sessions[sid]; this.type = (this.stream.getVideoTracks().length > 0) - ? this.VIDEO_TYPE : this.AUDIO_TYPE; + ? MediaStream.VIDEO_TYPE : MediaStream.AUDIO_TYPE; + this.muted = false; } return MediaStreamProto; -})(); \ No newline at end of file +})(); + +MediaStream.VIDEO_TYPE = 'Video'; +MediaStream.AUDIO_TYPE = 'Audio'; \ No newline at end of file diff --git a/muc.js b/muc.js index 0594613ee..430415c98 100644 --- a/muc.js +++ b/muc.js @@ -335,6 +335,14 @@ Strophe.addConnectionPlugin('emuc', { 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'}) @@ -456,5 +464,11 @@ Strophe.addConnectionPlugin('emuc', { }, addBridgeIsDownToPresence: function() { this.presMap['bridgeIsDown'] = true; + }, + addEmailToPresence: function(email) { + this.presMap['email'] = email; + }, + addUserIdToPresence: function(userId) { + this.presMap['userId'] = userId; } }); diff --git a/settings_menu.js b/settings_menu.js new file mode 100644 index 000000000..978836640 --- /dev/null +++ b/settings_menu.js @@ -0,0 +1,83 @@ +var SettingsMenu = (function(my) { + + var email = ''; + var displayName = ''; + var userId; + + if(supportsLocalStorage()) { + if(!window.localStorage.jitsiMeetId) { + window.localStorage.jitsiMeetId = generateUniqueId(); + console.log("generated id", window.localStorage.jitsiMeetId); + } + userId = window.localStorage.jitsiMeetId || ''; + email = window.localStorage.email || ''; + displayName = window.localStorage.displayname || ''; + } else { + console.log("local storage is not supported"); + userId = generateUniqueId(); + } + + my.update = function() { + var newDisplayName = Util.escapeHtml($('#setDisplayName').get(0).value); + if(newDisplayName) { + displayName = newDisplayName; + connection.emuc.addDisplayNameToPresence(displayName); + window.localStorage.displayname = displayName; + } + + var newEmail = Util.escapeHtml($('#setEmail').get(0).value); + connection.emuc.addEmailToPresence(newEmail); + email = newEmail; + window.localStorage.email = newEmail; + + connection.emuc.sendPresence(); + Avatar.setUserAvatar(connection.emuc.myroomjid, email); + }; + + my.isVisible = function() { + return $('#settingsmenu').is(':visible'); + }; + + my.getUID = function() { + return userId; + }; + + my.getEmail = function() { + return email; + }; + + my.getDisplayName = function() { + return displayName; + }; + + my.setDisplayName = function(newDisplayName) { + displayName = newDisplayName; + window.localStorage.displayname = displayName; + $('#setDisplayName').get(0).value = displayName; + }; + + function supportsLocalStorage() { + try { + return 'localStorage' in window && window.localStorage !== null; + } catch (e) { + console.log("localstorage is not supported"); + return false; + } + } + + function generateUniqueId() { + function _p8() { + return (Math.random().toString(16)+"000000000").substr(2,8); + } + return _p8() + _p8() + _p8() + _p8(); + } + + $(document).bind('displaynamechanged', function(event, peerJid, newDisplayName) { + if(peerJid === 'localVideoContainer' || + peerJid === connection.emuc.myroomjid) { + SettingsMenu.setDisplayName(newDisplayName); + } + }); + + return my; +}(SettingsMenu || {})); diff --git a/side_panel_toggler.js b/side_panel_toggler.js new file mode 100644 index 000000000..5716c9aa3 --- /dev/null +++ b/side_panel_toggler.js @@ -0,0 +1,245 @@ +/** + * Toggler for the chat, contact list, settings menu, etc.. + */ +var PanelToggler = (function(my) { + + var currentlyOpen = null; + var buttons = { + '#chatspace': '#chatBottomButton', + '#contactlist': '#contactListButton', + '#settingsmenu': '#settingsButton' + }; + + /** + * Resizes the video area + * @param isClosing whether the side panel is going to be closed or is going to open / remain opened + * @param completeFunction a function to be called when the video space is resized + */ + var resizeVideoArea = function(isClosing, completeFunction) { + var videospace = $('#videospace'); + + var panelSize = isClosing ? [0, 0] : PanelToggler.getPanelSize(); + var videospaceWidth = window.innerWidth - panelSize[0]; + var videospaceHeight = window.innerHeight; + var videoSize + = getVideoSize(null, null, videospaceWidth, videospaceHeight); + var videoWidth = videoSize[0]; + var videoHeight = videoSize[1]; + var videoPosition = getVideoPosition(videoWidth, + videoHeight, + videospaceWidth, + videospaceHeight); + var horizontalIndent = videoPosition[0]; + var verticalIndent = videoPosition[1]; + + var thumbnailSize = VideoLayout.calculateThumbnailSize(videospaceWidth); + var thumbnailsWidth = thumbnailSize[0]; + var thumbnailsHeight = thumbnailSize[1]; + //for chat + + videospace.animate({ + right: panelSize[0], + width: videospaceWidth, + height: videospaceHeight + }, + { + queue: false, + duration: 500, + complete: completeFunction + }); + + $('#remoteVideos').animate({ + height: thumbnailsHeight + }, + { + queue: false, + duration: 500 + }); + + $('#remoteVideos>span').animate({ + height: thumbnailsHeight, + width: thumbnailsWidth + }, + { + queue: false, + duration: 500, + complete: function () { + $(document).trigger( + "remotevideo.resized", + [thumbnailsWidth, + thumbnailsHeight]); + } + }); + + $('#largeVideoContainer').animate({ + width: videospaceWidth, + height: videospaceHeight + }, + { + queue: false, + duration: 500 + }); + + $('#largeVideo').animate({ + width: videoWidth, + height: videoHeight, + top: verticalIndent, + bottom: verticalIndent, + left: horizontalIndent, + right: horizontalIndent + }, + { + queue: false, + duration: 500 + }); + }; + + /** + * Toggles the windows in the side panel + * @param object the window that should be shown + * @param selector the selector for the element containing the panel + * @param onOpenComplete function to be called when the panel is opened + * @param onOpen function to be called if the window is going to be opened + * @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"); + + if (object.isVisible()) { + $("#toast-container").animate({ + right: '5px' + }, + { + queue: false, + duration: 500 + }); + $(selector).hide("slide", { + direction: "right", + queue: false, + duration: 500 + }); + if(typeof onClose === "function") { + onClose(); + } + + currentlyOpen = null; + } + else { + // Undock the toolbar when the chat is shown and if we're in a + // video mode. + if (VideoLayout.isLargeVideoVisible()) { + ToolbarToggler.dockToolbar(false); + } + + if(currentlyOpen) { + var current = $(currentlyOpen); + buttonClick(buttons[currentlyOpen], "active"); + current.css('z-index', 4); + setTimeout(function () { + current.css('display', 'none'); + current.css('z-index', 5); + }, 500); + } + + $("#toast-container").animate({ + right: (PanelToggler.getPanelSize()[0] + 5) + 'px' + }, + { + queue: false, + duration: 500 + }); + $(selector).show("slide", { + direction: "right", + queue: false, + duration: 500, + complete: onOpenComplete + }); + if(typeof onOpen === "function") { + onOpen(); + } + + currentlyOpen = selector; + } + }; + + /** + * Opens / closes the chat area. + */ + my.toggleChat = function() { + var chatCompleteFunction = Chat.isVisible() ? + function() {} : function () { + Chat.scrollChatToBottom(); + $('#chatspace').trigger('shown'); + }; + + resizeVideoArea(Chat.isVisible(), chatCompleteFunction); + + toggle(Chat, + '#chatspace', + function () { + // Request the focus in the nickname field or the chat input field. + if ($('#nickname').css('visibility') === 'visible') { + $('#nickinput').focus(); + } else { + $('#usermsg').focus(); + } + }, + null, + Chat.resizeChat, + null); + }; + + /** + * Opens / closes the contact list area. + */ + my.toggleContactList = function () { + var completeFunction = ContactList.isVisible() ? + function() {} : function () { $('#contactlist').trigger('shown');}; + resizeVideoArea(ContactList.isVisible(), completeFunction); + + toggle(ContactList, + '#contactlist', + null, + function() { + ContactList.setVisualNotification(false); + }, + null); + }; + + /** + * Opens / closes the settings menu + */ + my.toggleSettingsMenu = function() { + resizeVideoArea(SettingsMenu.isVisible(), function (){}); + toggle(SettingsMenu, + '#settingsmenu', + null, + function() { + $('#setDisplayName').get(0).value = SettingsMenu.getDisplayName(); + $('#setEmail').get(0).value = SettingsMenu.getEmail(); + }, + null); + }; + + /** + * Returns the size of the side panel. + */ + my.getPanelSize = function () { + var availableHeight = window.innerHeight; + var availableWidth = window.innerWidth; + + var panelWidth = 200; + if (availableWidth * 0.2 < 200) { + panelWidth = availableWidth * 0.2; + } + + return [panelWidth, availableHeight]; + }; + + my.isVisible = function() { + return (Chat.isVisible() || ContactList.isVisible() || SettingsMenu.isVisible()); + }; + + return my; + +}(PanelToggler || {})); diff --git a/util.js b/util.js index 652b5b14c..4e20096fd 100644 --- a/util.js +++ b/util.js @@ -51,12 +51,10 @@ var Util = (function (my) { * Returns the available video width. */ my.getAvailableVideoWidth = function () { - var chatspaceWidth - = (Chat.isVisible() || ContactList.isVisible()) - ? $('#chatspace').width() - : 0; + var rightPanelWidth + = PanelToggler.isVisible() ? PanelToggler.getPanelSize()[0] : 0; - return window.innerWidth - chatspaceWidth; + return window.innerWidth - rightPanelWidth; }; my.imageToGrayScale = function (canvas) { diff --git a/videolayout.js b/videolayout.js index 2d1ae1989..c2ac6ff5f 100644 --- a/videolayout.js +++ b/videolayout.js @@ -123,6 +123,7 @@ var VideoLayout = (function (my) { if ($('#largeVideo').attr('src') != newSrc) { + $('#activeSpeakerAvatar').css('visibility', 'hidden'); // Due to the simulcast the localVideoSrc may have changed when the // fadeOut event triggers. In that case the getJidFromVideoSrc and // isVideoSrcDesktop methods will not function correctly. @@ -155,6 +156,8 @@ var VideoLayout = (function (my) { var doUpdate = function () { + Avatar.updateActiveSpeakerAvatarSrc(largeVideoState.userJid); + if (!userChanged && largeVideoState.preload && largeVideoState.preload != null && $(largeVideoState.preload).attr('src') == newSrc) { @@ -227,6 +230,10 @@ var VideoLayout = (function (my) { $(this).fadeIn(300); } + if(userChanged) { + Avatar.showUserAvatar(largeVideoState.oldJid); + } + largeVideoState.updateInProgress = false; }; @@ -368,12 +375,13 @@ var VideoLayout = (function (my) { * in the document and creates it eventually. * * @param peerJid peer Jid to check. + * @param userId user email or id for setting the avatar * * @return Returns true if the peer container exists, * false - otherwise */ - my.ensurePeerContainerExists = function(peerJid) { - ContactList.ensureAddContact(peerJid); + my.ensurePeerContainerExists = function(peerJid, userId) { + ContactList.ensureAddContact(peerJid, userId); var resourceJid = Strophe.getResourceFromJid(peerJid); @@ -382,14 +390,15 @@ var VideoLayout = (function (my) { if ($('#' + videoSpanId).length > 0) { // If there's been a focus change, make sure we add focus related // interface!! - if (focus && $('#remote_popupmenu_' + resourceJid).length <= 0) - addRemoteVideoMenu( peerJid, - document.getElementById(videoSpanId)); + if (focus && $('#remote_popupmenu_' + resourceJid).length <= 0) { + addRemoteVideoMenu(peerJid, + document.getElementById(videoSpanId)); + } } else { - var container - = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId); - + var container = + VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId, userId); + Avatar.setUserAvatar(peerJid, userId); // Set default display name. setDisplayName(videoSpanId); @@ -468,8 +477,10 @@ var VideoLayout = (function (my) { var videoStream = simulcast.getReceivingVideoStream(stream); RTC.attachMediaStream(sel, videoStream); - if (isVideo) + if (isVideo) { waitForRemoteVideo(sel, thessrc, stream); + } + } stream.onended = function () { @@ -619,7 +630,7 @@ var VideoLayout = (function (my) { */ function setDisplayName(videoSpanId, displayName) { var nameSpan = $('#' + videoSpanId + '>span.displayname'); - var defaultLocalDisplayName = "Me"; + var defaultLocalDisplayName = interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME; // If we already have a display name for this video. if (nameSpan.length > 0) { @@ -680,6 +691,7 @@ var VideoLayout = (function (my) { .bind("click", function (e) { e.preventDefault(); + e.stopPropagation(); $('#localDisplayName').hide(); $('#editDisplayName').show(); $('#editDisplayName').focus(); @@ -698,7 +710,7 @@ var VideoLayout = (function (my) { }); } } - }; + } my.inputDisplayNameHandler = function (name) { if (nickname !== name) { @@ -715,7 +727,7 @@ var VideoLayout = (function (my) { $('#localDisplayName').text(nickname + " (me)"); else $('#localDisplayName') - .text(defaultLocalDisplayName); + .text(interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME); $('#localDisplayName').show(); } @@ -911,6 +923,10 @@ var VideoLayout = (function (my) { $('#largeVideoContainer').width(availableWidth); $('#largeVideoContainer').height(availableHeight); + + $('#activeSpeakerAvatar').css('top', + (availableHeight - interfaceConfig.ACTIVE_SPEAKER_AVATAR_SIZE) / 2); + VideoLayout.resizeThumbnails(); }; @@ -930,6 +946,8 @@ var VideoLayout = (function (my) { $('#remoteVideos>span').width(width); $('#remoteVideos>span').height(height); + $('.userAvatar').css('left', (width - height) / 2); + $(document).trigger("remotevideo.resized", [width, height]); }; @@ -1527,36 +1545,27 @@ var VideoLayout = (function (my) { if (!isVisible) { console.log("Add to last N", resourceJid); - mediaStreams.some(function (mediaStream) { - if (mediaStream.peerjid - && Strophe.getResourceFromJid(mediaStream.peerjid) - === resourceJid - && mediaStream.type === mediaStream.VIDEO_TYPE) { - var sel = $('#participant_' + resourceJid + '>video'); + var jid = connection.emuc.findJidFromResource(resourceJid); + var mediaStream = mediaStreams[jid][MediaStream.VIDEO_TYPE]; + var sel = $('#participant_' + resourceJid + '>video'); - var videoStream = simulcast.getReceivingVideoStream(mediaStream.stream); - RTC.attachMediaStream(sel, videoStream); - videoSrcToSsrc[sel.attr('src')] = mediaStream.ssrc; - if (lastNPickupJid == mediaStream.peerjid) { - // Clean up the lastN pickup jid. - lastNPickupJid = null; + var videoStream = simulcast.getReceivingVideoStream( + mediaStream.stream); + RTC.attachMediaStream(sel, videoStream); + videoSrcToSsrc[sel.attr('src')] = mediaStream.ssrc; + if (lastNPickupJid == mediaStream.peerjid) { + // Clean up the lastN pickup jid. + lastNPickupJid = null; - // Don't fire the events again, they've already - // been fired in the contact list click handler. - VideoLayout.handleVideoThumbClicked($(sel).attr('src'), false); + // Don't fire the events again, they've already + // been fired in the contact list click handler. + VideoLayout.handleVideoThumbClicked($(sel).attr('src'), false); - updateLargeVideo = false; - } - - waitForRemoteVideo( - sel, - mediaStream.ssrc, - mediaStream.stream); - return true; - } - }); + updateLargeVideo = false; + } + waitForRemoteVideo(sel, mediaStream.ssrc, mediaStream.stream); } - }); + }) } // The endpoint that was being shown in the large video has dropped out