diff --git a/app.js b/app.js index 549ebea676..bb7f6e5472 100644 --- a/app.js +++ b/app.js @@ -46,8 +46,6 @@ var getVideoPosition; /* window.onbeforeunload = closePageWarning; */ -var preMuted = false; - var sessionTerminated = false; function init() { @@ -123,24 +121,25 @@ function obtainAudioAndVideoPermissions(callback) { function audioStreamReady(stream) { - change_local_audio(stream); + VideoLayout.changeLocalAudio(stream); if (RTC.browser !== 'firefox') { - getUserMediaWithConstraints(['video'], videoStreamReady, videoStreamFailed, config.resolution || '360'); + getUserMediaWithConstraints(['video'], + videoStreamReady, + videoStreamFailed, + config.resolution || '360'); } else { doJoin(); } } function videoStreamReady(stream) { - - change_local_video(stream, true); + VideoLayout.changeLocalVideo(stream, true); doJoin(); } function videoStreamFailed(error) { - console.warn("Failed to obtain video stream - continue anyway", error); doJoin(); @@ -189,52 +188,6 @@ function doJoin() { connection.emuc.doJoin(roomjid); } -function change_local_audio(stream) { - connection.jingle.localAudio = 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) { - - connection.jingle.localVideo = stream; - - var localVideo = document.createElement('video'); - localVideo.id = 'localVideo_' + stream.id; - localVideo.autoplay = true; - localVideo.volume = 0; // is it required if audio is separated ? - localVideo.oncontextmenu = function () { return false; }; - - var localVideoContainer = document.getElementById('localVideoWrapper'); - localVideoContainer.appendChild(localVideo); - - var localVideoSelector = $('#' + localVideo.id); - // Add click handler - localVideoSelector.click(function () { - handleVideoThumbClicked(localVideo.src); - }); - // Add stream ended handler - stream.onended = function () { - localVideoContainer.removeChild(localVideo); - checkChangeLargeVideo(localVideo.src); - }; - // Flip video x axis if needed - flipXLocalVideo = flipX; - if (flipX) { - localVideoSelector.addClass("flipVideoX"); - } - // Attach WebRTC stream - RTC.attachMediaStream(localVideoSelector, stream); - - localVideoSrc = localVideo.src; - updateLargeVideo(localVideoSrc, 0); -} - $(document).bind('remotestreamadded.jingle', function (event, data, sid) { function waitForRemoteVideo(selector, sid, ssrc) { if (selector.removed) { @@ -286,7 +239,7 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) { var remotes = document.getElementById('remoteVideos'); if (data.peerjid) { - ensurePeerContainerExists(data.peerjid); + VideoLayout.ensurePeerContainerExists(data.peerjid); container = document.getElementById( 'participant_' + Strophe.getResourceFromJid(data.peerjid)); } else { @@ -340,16 +293,26 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) { // Remove whole container container.remove(); Util.playSoundNotification('userLeft'); - resizeThumbnails(); + VideoLayout.resizeThumbnails(); } - checkChangeLargeVideo(vid.src); + VideoLayout.checkChangeLargeVideo(vid.src); }; // Add click handler sel.click(function () { - handleVideoThumbClicked(vid.src); + VideoLayout.handleVideoThumbClicked(vid.src); }); + // Add hover handler + sel.hover( + function() { + VideoLayout.showDisplayName(container.id, true); + }, + function() { + if (focusedVideoSrc !== vid.src) + VideoLayout.showDisplayName(container.id, false); + } + ); // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 if (isVideo && @@ -382,89 +345,6 @@ function getJidFromVideoSrc(videoSrc) return ssrc2jid[ssrc]; } -/** - * Gets the selector of video thumbnail container for the user identified by - * given userJid - * @param userJid user's Jid for whom we want to get the video container. - */ -function getParticipantContainer(userJid) -{ - if (!userJid) - return null; - - if (userJid === connection.emuc.myroomjid) - return $("#localVideoContainer"); - else - return $("#participant_" + Strophe.getResourceFromJid(userJid)); -} - -function handleVideoThumbClicked(videoSrc) { - // Restore style for previously focused video - var oldContainer = - getParticipantContainer( - getJidFromVideoSrc(focusedVideoSrc)); - if (oldContainer) - oldContainer.removeClass("videoContainerFocused"); - - // Unlock - if (focusedVideoSrc === videoSrc) - { - focusedVideoSrc = null; - return; - } - - // Lock new video - focusedVideoSrc = videoSrc; - - var userJid = getJidFromVideoSrc(videoSrc); - if (userJid) - { - var container = getParticipantContainer(userJid); - container.addClass("videoContainerFocused"); - } - - $(document).trigger("video.selected", [false]); - - updateLargeVideo(videoSrc, 1); - - $('audio').each(function (idx, el) { - if (el.id.indexOf('mixedmslabel') !== -1) { - el.volume = 0; - el.volume = 1; - } - }); -} - -/** - * Checks if removed video is currently displayed and tries to display another one instead. - * @param removedVideoSrc src stream identifier of the video. - */ -function checkChangeLargeVideo(removedVideoSrc) { - if (removedVideoSrc === $('#largeVideo').attr('src')) { - // this is currently displayed as large - // pick the last visible video in the row - // if nobody else is left, this picks the local video - var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video').get(0); - - if (!pick) { - console.info("Last visible video no longer exists"); - pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0); - if (!pick) { - // Try local video - console.info("Fallback to local video..."); - pick = $('#remoteVideos>span>span>video').get(0); - } - } - - // mute if localvideo - if (pick) { - updateLargeVideo(pick.src, pick.volume); - } else { - console.warn("Failed to elect large video"); - } - } -} - // an attempt to work around https://github.com/jitsi/jitmeet/issues/32 function sendKeyframe(pc) { console.log('sendkeyframe', pc.iceConnectionState); @@ -495,7 +375,7 @@ function sendKeyframe(pc) { ); } -// really mute video, i.e. dont even send black frames +// Really mute video, i.e. dont even send black frames function muteVideo(pc, unmute) { // FIXME: this probably needs another of those lovely state safeguards... // which checks for iceconn == connected and sigstate == stable @@ -589,7 +469,10 @@ $(document).bind('callincoming.jingle', function (event, sid) { $(document).bind('conferenceCreated.jingle', function (event, focus) { startRtpStatsCollector(); +}); +$(document).bind('conferenceCreated.jingle', function (event, focus) +{ // Bind data channel listener in case we're the focus if (config.openSctp) { @@ -601,12 +484,12 @@ $(document).bind('callactive.jingle', function (event, videoelem, sid) { if (videoelem.attr('id').indexOf('mixedmslabel') === -1) { // ignore mixedmslabela0 and v0 videoelem.show(); - resizeThumbnails(); + VideoLayout.resizeThumbnails(); if (!focusedVideoSrc) - updateLargeVideo(videoelem.attr('src'), 1); + VideoLayout.updateLargeVideo(videoelem.attr('src'), 1); - showFocusIndicator(); + VideoLayout.showFocusIndicator(); } }); @@ -675,16 +558,16 @@ $(document).bind('joined.muc', function (event, jid, info) { Etherpad.init(); } - showFocusIndicator(); + VideoLayout.showFocusIndicator(); // Once we've joined the muc show the toolbar - showToolbar(); + Toolbar.showToolbar(); var displayName = ''; if (info.displayName) displayName = info.displayName + ' (me)'; - showDisplayName('localVideoContainer', displayName); + VideoLayout.setDisplayName('localVideoContainer', displayName); }); $(document).bind('entered.muc', function (event, jid, info, pres) { @@ -692,7 +575,7 @@ $(document).bind('entered.muc', function (event, jid, info, pres) { console.log('is focus?' + focus ? 'true' : 'false'); // Add Peer's container - ensurePeerContainerExists(jid); + VideoLayout.ensurePeerContainerExists(jid); if (focus !== null) { // FIXME: this should prepare the video @@ -705,7 +588,7 @@ $(document).bind('entered.muc', function (event, jid, info, pres) { } } else if (sharedKey) { - updateLockButton(); + Toolbar.updateLockButton(); } }); @@ -719,7 +602,7 @@ $(document).bind('left.muc', function (event, jid) { if (container) { // hide here, wait for video to close before removing $(container).hide(); - resizeThumbnails(); + VideoLayout.resizeThumbnails(); } }, 10); @@ -792,7 +675,7 @@ $(document).bind('presence.muc', function (event, jid, info, pres) { case 'recvonly': el.hide(); // FIXME: Check if we have to change large video - //checkChangeLargeVideo(el); + //VideoLayout.checkChangeLargeVideo(el); break; } } @@ -800,11 +683,13 @@ $(document).bind('presence.muc', function (event, jid, info, pres) { if (info.displayName) { if (jid === connection.emuc.myroomjid) { - showDisplayName('localVideoContainer', info.displayName + ' (me)'); + VideoLayout.setDisplayName('localVideoContainer', + info.displayName + ' (me)'); } else { - ensurePeerContainerExists(jid); - showDisplayName('participant_' + Strophe.getResourceFromJid(jid), - info.displayName); + VideoLayout.ensurePeerContainerExists(jid); + VideoLayout.setDisplayName( + 'participant_' + Strophe.getResourceFromJid(jid), + info.displayName); } } }); @@ -833,75 +718,10 @@ $(document).bind('passwordrequired.muc', function (event, jid) { }); }); -$(document).bind('audiomuted.muc', function (event, jid, isMuted) { - var videoSpanId = null; - if (jid === connection.emuc.myroomjid) { - videoSpanId = 'localVideoContainer'; - } else { - ensurePeerContainerExists(jid); - videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid); - } - - if (focus) { - mutedAudios[jid] = isMuted; - updateRemoteVideoMenu(jid, isMuted); - } - - if (videoSpanId) - showAudioIndicator(videoSpanId, isMuted); -}); - -$(document).bind('videomuted.muc', function (event, jid, isMuted) { - var videoSpanId = null; - if (jid === connection.emuc.myroomjid) { - videoSpanId = 'localVideoContainer'; - } else { - ensurePeerContainerExists(jid); - videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid); - } - - if (videoSpanId) - showVideoIndicator(videoSpanId, isMuted); -}); - -/** - * Updates the large video with the given new video source. - */ -function updateLargeVideo(newSrc, vol) { - console.log('hover in', newSrc); - - if ($('#largeVideo').attr('src') != newSrc) { - - var isVisible = $('#largeVideo').is(':visible'); - - $('#largeVideo').fadeOut(300, function () { - $(this).attr('src', newSrc); - - // Screen stream is already rotated - var flipX = (newSrc === localVideoSrc) && flipXLocalVideo; - - var videoTransform = document.getElementById('largeVideo').style.webkitTransform; - if (flipX && videoTransform !== 'scaleX(-1)') { - document.getElementById('largeVideo').style.webkitTransform = "scaleX(-1)"; - } - else if (!flipX && videoTransform === 'scaleX(-1)') { - document.getElementById('largeVideo').style.webkitTransform = "none"; - } - - // Change the way we'll be measuring and positioning large video - var isDesktop = isVideoSrcDesktop(newSrc); - getVideoSize = isDesktop ? getDesktopVideoSize : getCameraVideoSize; - getVideoPosition = isDesktop ? getDesktopVideoPosition : getCameraVideoPosition; - - if (isVisible) - $(this).fadeIn(300); - }); - } -} - /** * Checks if video identified by given src is desktop stream. - * @param videoSrc eg. blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395 + * @param videoSrc eg. + * blob:https%3A//pawel.jitsi.net/9a46e0bd-131e-4d18-9c14-a9264e8db395 * @returns {boolean} */ function isVideoSrcDesktop(videoSrc) { @@ -929,20 +749,6 @@ function isVideoSrcDesktop(videoSrc) { return isDesktop; } -/** - * Shows/hides the large video. - */ -function setLargeVideoVisible(isVisible) { - if (isVisible) { - $('#largeVideo').css({visibility: 'visible'}); - $('.watermark').css({visibility: 'visible'}); - } - else { - $('#largeVideo').css({visibility: 'hidden'}); - $('.watermark').css({visibility: 'hidden'}); - } -} - function getConferenceHandler() { return focus ? focus : activecall; } @@ -1002,38 +808,6 @@ function toggleAudio() { buttonClick("#mute", "icon-microphone icon-mic-disabled"); } -/** - * Positions the large video. - * - * @param videoWidth the stream video width - * @param videoHeight the stream video height - */ -var positionLarge = function (videoWidth, videoHeight) { - var videoSpaceWidth = $('#videospace').width(); - var videoSpaceHeight = window.innerHeight; - - var videoSize = getVideoSize(videoWidth, - videoHeight, - videoSpaceWidth, - videoSpaceHeight); - - var largeVideoWidth = videoSize[0]; - var largeVideoHeight = videoSize[1]; - - var videoPosition = getVideoPosition(largeVideoWidth, - largeVideoHeight, - videoSpaceWidth, - videoSpaceHeight); - - var horizontalIndent = videoPosition[0]; - var verticalIndent = videoPosition[1]; - - positionVideo($('#largeVideo'), - largeVideoWidth, - largeVideoHeight, - horizontalIndent, verticalIndent); -}; - /** * Returns an array of the video horizontal and vertical indents, * so that if fits its parent. @@ -1112,113 +886,6 @@ function getCameraVideoSize(videoWidth, return [availableWidth, availableHeight]; } -/** - * Returns an array of the video dimensions, so that it keeps it's aspect ratio and fits available area with it's - * larger dimension. This method ensures that whole video will be visible and can leave empty areas. - * - * @return an array with 2 elements, the video width and the video height - */ -function getDesktopVideoSize(videoWidth, - videoHeight, - videoSpaceWidth, - videoSpaceHeight) { - if (!videoWidth) - videoWidth = currentVideoWidth; - if (!videoHeight) - videoHeight = currentVideoHeight; - - var aspectRatio = videoWidth / videoHeight; - - var availableWidth = Math.max(videoWidth, videoSpaceWidth); - var availableHeight = Math.max(videoHeight, videoSpaceHeight); - - videoSpaceHeight -= $('#remoteVideos').outerHeight(); - - if (availableWidth / aspectRatio >= videoSpaceHeight) - { - availableHeight = videoSpaceHeight; - availableWidth = availableHeight * aspectRatio; - } - - if (availableHeight * aspectRatio >= videoSpaceWidth) - { - availableWidth = videoSpaceWidth; - availableHeight = availableWidth / aspectRatio; - } - - return [availableWidth, availableHeight]; -} - -/** - * Sets the size and position of the given video element. - * - * @param video the video element to position - * @param width the desired video width - * @param height the desired video height - * @param horizontalIndent the left and right indent - * @param verticalIndent the top and bottom indent - */ -function positionVideo(video, - width, - height, - horizontalIndent, - verticalIndent) { - video.width(width); - video.height(height); - video.css({ top: verticalIndent + 'px', - bottom: verticalIndent + 'px', - left: horizontalIndent + 'px', - right: horizontalIndent + 'px'}); -} - -var resizeLargeVideoContainer = function () { - Chat.resizeChat(); - var availableHeight = window.innerHeight; - var availableWidth = Util.getAvailableVideoWidth(); - - if (availableWidth < 0 || availableHeight < 0) return; - - $('#videospace').width(availableWidth); - $('#videospace').height(availableHeight); - $('#largeVideoContainer').width(availableWidth); - $('#largeVideoContainer').height(availableHeight); - - resizeThumbnails(); -}; - -var calculateThumbnailSize = function () { - // Calculate the available height, which is the inner window height minus - // 39px for the header minus 2px for the delimiter lines on the top and - // bottom of the large video, minus the 36px space inside the remoteVideos - // container used for highlighting shadow. - var availableHeight = 100; - - var numvids = $('#remoteVideos>span:visible').length; - - // Remove the 1px borders arround videos and the chat width. - var availableWinWidth = $('#remoteVideos').width() - 2 * numvids - 50; - var availableWidth = availableWinWidth / numvids; - var aspectRatio = 16.0 / 9.0; - var maxHeight = Math.min(160, availableHeight); - availableHeight = Math.min(maxHeight, availableWidth / aspectRatio); - if (availableHeight < availableWidth / aspectRatio) { - availableWidth = Math.floor(availableHeight * aspectRatio); - } - - return [availableWidth, availableHeight]; -}; - -function resizeThumbnails() { - var thumbnailSize = calculateThumbnailSize(); - var width = thumbnailSize[0]; - var height = thumbnailSize[1]; - - // size videos so that while keeping AR and max height, we have a nice fit - $('#remoteVideos').height(height); - $('#remoteVideos>span').width(width); - $('#remoteVideos>span').height(height); -} - $(document).ready(function () { Chat.init(); @@ -1236,17 +903,17 @@ $(document).ready(function () { getVideoSize = getCameraVideoSize; getVideoPosition = getCameraVideoPosition; - resizeLargeVideoContainer(); + VideoLayout.resizeLargeVideoContainer(); $(window).resize(function () { - resizeLargeVideoContainer(); - positionLarge(); + VideoLayout.resizeLargeVideoContainer(); + VideoLayout.positionLarge(); }); // Listen for large video size updates document.getElementById('largeVideo') .addEventListener('loadedmetadata', function (e) { currentVideoWidth = this.videoWidth; currentVideoHeight = this.videoHeight; - positionLarge(currentVideoWidth, currentVideoHeight); + VideoLayout.positionLarge(currentVideoWidth, currentVideoHeight); }); if (!$('#settings').is(':visible')) { @@ -1356,167 +1023,6 @@ function openMessageDialog(titleString, messageString) { ); } -/** - * Opens the lock room dialog. - */ -function openLockDialog() { - // Only the focus is able to set a shared key. - if (focus === null) { - if (sharedKey) - $.prompt("This conversation is currently protected by a shared secret key.", - { - title: "Secrect key", - persistent: false - } - ); - else - $.prompt("This conversation isn't currently protected by a secret key. Only the owner of the conference could set a shared key.", - { - title: "Secrect key", - persistent: false - } - ); - } else { - if (sharedKey) { - $.prompt("Are you sure you would like to remove your secret key?", - { - title: "Remove secrect key", - persistent: false, - buttons: { "Remove": true, "Cancel": false}, - defaultButton: 1, - submit: function (e, v, m, f) { - if (v) { - setSharedKey(''); - lockRoom(false); - } - } - } - ); - } else { - $.prompt('

Set a secrect key to lock your room

' + - '', - { - persistent: false, - buttons: { "Save": true, "Cancel": false}, - defaultButton: 1, - loaded: function (event) { - document.getElementById('lockKey').focus(); - }, - submit: function (e, v, m, f) { - if (v) { - var lockKey = document.getElementById('lockKey'); - - if (lockKey.value) { - setSharedKey(Util.escapeHtml(lockKey.value)); - lockRoom(true); - } - } - } - } - ); - } - } -} - -/** - * Opens the invite link dialog. - */ -function openLinkDialog() { - var inviteLink; - if (roomUrl == null) - inviteLink = "Your conference is currently being created..."; - else - inviteLink = encodeURI(roomUrl); - - $.prompt('', - { - title: "Share this link with everyone you want to invite", - persistent: false, - buttons: { "Invite": true, "Cancel": false}, - defaultButton: 1, - loaded: function (event) { - if (roomUrl) - document.getElementById('inviteLinkRef').select(); - else - document.getElementById('jqi_state0_buttonInvite') - .disabled = true; - }, - submit: function (e, v, m, f) { - if (v) { - if (roomUrl) { - inviteParticipants(); - } - } - } - } - ); -} - -/** - * Invite participants to conference. - */ -function inviteParticipants() { - if (roomUrl == null) - return; - - var conferenceName = roomUrl.substring(roomUrl.lastIndexOf('/') + 1); - var subject = "Invitation to a Jitsi Meet (" + conferenceName + ")"; - var body = "Hey there, I%27d like to invite you to a Jitsi Meet" - + " conference I%27ve just set up.%0D%0A%0D%0A" - + "Please click on the following link in order" - + " to join the conference.%0D%0A%0D%0A" - + roomUrl + "%0D%0A%0D%0A" - + "Note that Jitsi Meet is currently only supported by Chromim," - + " Google Chrome and Opera, so you need" - + " to be using one of these browsers.%0D%0A%0D%0A" - + "Talk to you in a sec!"; - - if (window.localStorage.displayname) - body += "%0D%0A%0D%0A" + window.localStorage.displayname; - - window.open("mailto:?subject=" + subject + "&body=" + body, '_blank'); -} - -/** - * Opens the settings dialog. - */ -function openSettingsDialog() { - $.prompt('

Configure your conference

' + - ' Participants join muted
' + - ' Require nicknames

' + - 'Set a secrect key to lock your room: ', - { - persistent: false, - buttons: { "Save": true, "Cancel": false}, - defaultButton: 1, - loaded: function (event) { - document.getElementById('lockKey').focus(); - }, - submit: function (e, v, m, f) { - if (v) { - if ($('#initMuted').is(":checked")) { - // it is checked - } - - if ($('#requireNicknames').is(":checked")) { - // it is checked - } - /* - var lockKey = document.getElementById('lockKey'); - - if (lockKey.value) - { - setSharedKey(lockKey.value); - lockRoom(true); - } - */ - } - } - } - ); -} - /** * Locks / unlocks the room. */ @@ -1526,7 +1032,7 @@ function lockRoom(lock) { else connection.emuc.lockRoom(''); - updateLockButton(); + Toolbar.updateLockButton(); } /** @@ -1536,86 +1042,6 @@ function setSharedKey(sKey) { sharedKey = sKey; } -/** - * Updates the lock button state. - */ -function updateLockButton() { - buttonClick("#lockIcon", "icon-security icon-security-locked"); -} - -/** - * Hides the toolbar. - */ -var hideToolbar = function () { - - var isToolbarHover = false; - $('#header').find('*').each(function () { - var id = $(this).attr('id'); - if ($("#" + id + ":hover").length > 0) { - isToolbarHover = true; - } - }); - - clearTimeout(toolbarTimeout); - toolbarTimeout = null; - - if (!isToolbarHover) { - $('#header').hide("slide", { direction: "up", duration: 300}); - } - else { - toolbarTimeout = setTimeout(hideToolbar, 2000); - } -}; - -/** - * Shows the call main toolbar. - */ -function showToolbar() { - if (!$('#header').is(':visible')) { - $('#header').show("slide", { direction: "up", duration: 300}); - - if (toolbarTimeout) { - clearTimeout(toolbarTimeout); - toolbarTimeout = null; - } - toolbarTimeout = setTimeout(hideToolbar, 2000); - } - - if (focus != null) - { -// TODO: Enable settings functionality. Need to uncomment the settings button in index.html. -// $('#settingsButton').css({visibility:"visible"}); - } - - // Show/hide desktop sharing button - showDesktopSharingButton(); -} - -/** - * Docks/undocks the toolbar. - * - * @param isDock indicates what operation to perform - */ -function dockToolbar(isDock) { - if (isDock) { - // First make sure the toolbar is shown. - if (!$('#header').is(':visible')) { - showToolbar(); - } - // Then clear the time out, to dock the toolbar. - clearTimeout(toolbarTimeout); - toolbarTimeout = null; - } - else { - if (!$('#header').is(':visible')) { - showToolbar(); - } - else { - toolbarTimeout = setTimeout(hideToolbar, 2000); - } - } -} - /** * Updates the room invite url. */ @@ -1636,378 +1062,19 @@ function updateRoomUrl(newRoomUrl) { */ function closePageWarning() { if (focus !== null) - return "You are the owner of this conference call and you are about to end it."; + return "You are the owner of this conference call and" + + " you are about to end it."; else return "You are about to leave this conversation."; } -/** - * Shows a visual indicator for the focus of the conference. - * Currently if we're not the owner of the conference we obtain the focus - * from the connection.jingle.sessions. - */ -function showFocusIndicator() { - if (focus !== null) { - var indicatorSpan = $('#localVideoContainer .focusindicator'); - - if (indicatorSpan.children().length === 0) - { - createFocusIndicatorElement(indicatorSpan[0]); - } - } - else if (Object.keys(connection.jingle.sessions).length > 0) { - // If we're only a participant the focus will be the only session we have. - var session - = connection.jingle.sessions - [Object.keys(connection.jingle.sessions)[0]]; - var focusId - = 'participant_' + Strophe.getResourceFromJid(session.peerjid); - var focusContainer = document.getElementById(focusId); - if (!focusContainer) { - console.error("No focus container!"); - return; - } - var indicatorSpan = $('#' + focusId + ' .focusindicator'); - - if (!indicatorSpan || indicatorSpan.length === 0) { - indicatorSpan = document.createElement('span'); - indicatorSpan.className = 'focusindicator'; - focusContainer.appendChild(indicatorSpan); - - createFocusIndicatorElement(indicatorSpan); - } - } -} - -/** - * Checks if container for participant identified by given peerJid exists in the document and creates it eventually. - * @param peerJid peer Jid to check. - */ -function ensurePeerContainerExists(peerJid) { - var peerResource = Strophe.getResourceFromJid(peerJid); - var videoSpanId = 'participant_' + peerResource; - - if ($('#' + videoSpanId).length > 0) { - // If there's been a focus change, make sure we add focus related - // interface!! - if (focus && $('#remote_popupmenu_' + peerResource).length <= 0) - addRemoteVideoMenu( peerJid, - document.getElementById(videoSpanId)); - return; - } - - var container = addRemoteVideoContainer(peerJid, videoSpanId); - - var nickfield = document.createElement('span'); - nickfield.className = "nick"; - nickfield.appendChild(document.createTextNode(peerResource)); - container.appendChild(nickfield); - resizeThumbnails(); -} - -function addRemoteVideoContainer(peerJid, spanId) { - var container = document.createElement('span'); - container.id = spanId; - container.className = 'videocontainer'; - var remotes = document.getElementById('remoteVideos'); - - // If the peerJid is null then this video span couldn't be directly - // associated with a participant (this could happen in the case of prezi). - if (focus && peerJid != null) - addRemoteVideoMenu(peerJid, container); - - remotes.appendChild(container); - return container; -} - -/** - * Creates the element indicating the focus of the conference. - */ -function createFocusIndicatorElement(parentElement) { - var focusIndicator = document.createElement('i'); - focusIndicator.className = 'fa fa-star'; - focusIndicator.title = "The owner of this conference"; - 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); - -// - 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 = ""; - - 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 = ""; - - 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 = ""; - - 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). - */ -function toggleFullScreen() { - var fsElement = document.documentElement; - - if (!document.mozFullScreen && !document.webkitIsFullScreen) { - //Enter Full Screen - if (fsElement.mozRequestFullScreen) { - fsElement.mozRequestFullScreen(); - } - else { - fsElement.webkitRequestFullScreen(Element.ALLOW_KEYBOARD_INPUT); - } - } else { - //Exit Full Screen - if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } else { - document.webkitCancelFullScreen(); - } - } -} - -/** - * Shows the display name for the given video. - */ -function showDisplayName(videoSpanId, displayName) { - var nameSpan = $('#' + videoSpanId + '>span.displayname'); - - // If we already have a display name for this video. - if (nameSpan.length > 0) { - var nameSpanElement = nameSpan.get(0); - - if (nameSpanElement.id === 'localDisplayName' && - $('#localDisplayName').text() !== displayName) { - $('#localDisplayName').text(displayName); - } else { - $('#' + videoSpanId + '_name').text(displayName); - } - } else { - var editButton = null; - - if (videoSpanId === 'localVideoContainer') { - editButton = createEditDisplayNameButton(); - } - if (displayName.length) { - nameSpan = document.createElement('span'); - nameSpan.className = 'displayname'; - nameSpan.innerText = displayName; - $('#' + videoSpanId)[0].appendChild(nameSpan); - } - - if (!editButton) { - nameSpan.id = videoSpanId + '_name'; - } else { - nameSpan.id = 'localDisplayName'; - $('#' + videoSpanId)[0].appendChild(editButton); - - var editableText = document.createElement('input'); - editableText.className = 'displayname'; - editableText.id = 'editDisplayName'; - - if (displayName.length) { - editableText.value = displayName.substring(0, displayName.indexOf(' (me)')); - } - - editableText.setAttribute('style', 'display:none;'); - editableText.setAttribute('placeholder', 'ex. Jane Pink'); - $('#' + videoSpanId)[0].appendChild(editableText); - - $('#localVideoContainer .displayname').bind("click", function (e) { - e.preventDefault(); - $('#localDisplayName').hide(); - $('#editDisplayName').show(); - $('#editDisplayName').focus(); - $('#editDisplayName').select(); - - var inputDisplayNameHandler = function (name) { - if (nickname !== name) { - nickname = name; - window.localStorage.displayname = nickname; - connection.emuc.addDisplayNameToPresence(nickname); - connection.emuc.sendPresence(); - - Chat.setChatConversationMode(true); - } - - if (!$('#localDisplayName').is(":visible")) { - $('#localDisplayName').text(nickname + " (me)"); - $('#localDisplayName').show(); - $('#editDisplayName').hide(); - } - }; - - $('#editDisplayName').one("focusout", function (e) { - inputDisplayNameHandler(this.value); - }); - - $('#editDisplayName').on('keydown', function (e) { - if (e.keyCode === 13) { - e.preventDefault(); - inputDisplayNameHandler(this.value); - } - }); - }); - } - } -} - -/** - * Creates the edit display name button. - * - * @returns the edit button - */ -function createEditDisplayNameButton() { - var editButton = document.createElement('a'); - editButton.className = 'displayname'; - editButton.innerHTML = ''; - - return editButton; -} - -/** - * Shows audio muted indicator over small videos. - */ -function showAudioIndicator(videoSpanId, isMuted) { - var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted'); - - if (isMuted === 'false') { - if (audioMutedSpan.length > 0) { - audioMutedSpan.remove(); - } - } - else { - var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted'); - - audioMutedSpan = document.createElement('span'); - audioMutedSpan.className = 'audioMuted'; - if (videoMutedSpan) { - audioMutedSpan.right = '30px'; - } - $('#' + videoSpanId)[0].appendChild(audioMutedSpan); - - var mutedIndicator = document.createElement('i'); - mutedIndicator.className = 'icon-mic-disabled'; - mutedIndicator.title = "Participant is muted"; - audioMutedSpan.appendChild(mutedIndicator); - } -} - -/** - * Shows video muted indicator over small videos. - */ -function showVideoIndicator(videoSpanId, isMuted) { - var videoMutedSpan = $('#' + videoSpanId + '>span.videoMuted'); - - if (isMuted === 'false') { - if (videoMutedSpan.length > 0) { - videoMutedSpan.remove(); - } - } - else { - var audioMutedSpan = $('#' + videoSpanId + '>span.audioMuted'); - - videoMutedSpan = document.createElement('span'); - videoMutedSpan.className = 'videoMuted'; - if (audioMutedSpan) { - videoMutedSpan.right = '30px'; - } - $('#' + videoSpanId)[0].appendChild(videoMutedSpan); - - var mutedIndicator = document.createElement('i'); - mutedIndicator.className = 'icon-camera-disabled'; - mutedIndicator.title = "Participant has stopped the camera."; - videoMutedSpan.appendChild(mutedIndicator); - } -} - /** * Resizes and repositions videos in full screen mode. */ $(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', function () { - resizeLargeVideoContainer(); - positionLarge(); + VideoLayout.resizeLargeVideoContainer(); + VideoLayout.positionLarge(); isFullScreen = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen; diff --git a/chat.js b/chat.js index ad7e50ba5c..49acbcac12 100644 --- a/chat.js +++ b/chat.js @@ -242,7 +242,7 @@ var Chat = (function (my) { if (unreadMessages) { unreadMsgElement.innerHTML = unreadMessages.toString(); - showToolbar(); + Toolbar.showToolbar(); var chatButtonElement = document.getElementById('chatButton').parentNode; diff --git a/css/videolayout_default.css b/css/videolayout_default.css index 44011d0274..e31775339f 100644 --- a/css/videolayout_default.css +++ b/css/videolayout_default.css @@ -34,7 +34,8 @@ border: 2px solid #212425; } -#remoteVideos .videocontainer:hover { +#remoteVideos .videocontainer:hover, +#remoteVideos .videocontainer.videoContainerFocused { width: 100%; height: 100%; content:""; @@ -52,10 +53,6 @@ z-index: 3; } -#remoteVideos .videocontainer.videoContainerFocused { - border: 3px solid #388396; -} - #localVideoWrapper { display:inline-block; -webkit-mask-box-image: url(../images/videomask.svg); @@ -63,7 +60,8 @@ border: 0px !important; } -#remoteVideos .videocontainer>video { +#remoteVideos .videocontainer>video, +#remoteVideos .videocontainer>canvas { border-radius:4px; } @@ -88,7 +86,8 @@ #etherpad, #localVideoWrapper>video, #localVideoWrapper, -.videocontainer>video { +.videocontainer>video, +.videocontainer>canvas { position: absolute; left: 0; top: 0; @@ -137,25 +136,22 @@ .videocontainer>span.displayname, .videocontainer>input.displayname { - display: inline-block; + display: none; position: absolute; - background: -webkit-linear-gradient(left, rgba(0,0,0,.7), rgba(0,0,0,0)); color: #FFFFFF; - bottom: 0; - left: 0; - padding: 3px 5px; - width: 100%; - height: auto; - max-height: 18px; - font-size: 9pt; - text-align: left; + background: rgba(0,0,0,.7); + text-align: center; text-overflow: ellipsis; + width: 70%; + height: 20%; + left: 15%; + top: 40%; + padding: 5px; + font-size: 11pt; overflow: hidden; white-space: nowrap; z-index: 2; - box-sizing: border-box; - border-bottom-left-radius:4px; - border-bottom-right-radius:4px; + border-radius:20px; } #localVideoContainer>span.displayname:hover { @@ -166,6 +162,10 @@ pointer-events: none; } +.videocontainer>input.displayname { + height: auto; +} + #localDisplayName { pointer-events: auto !important; } diff --git a/data_channels.js b/data_channels.js index 056345ea6b..aba083a469 100644 --- a/data_channels.js +++ b/data_channels.js @@ -38,14 +38,17 @@ function onDataChannel(event) var container = document.getElementById( 'participant_' + endpointId); + // Local video will not have container found, but that's ok // since we don't want to switch to local video + if (container) { var video = container.getElementsByTagName("video"); if (video.length) { - updateLargeVideo(video[0].src); + VideoLayout.updateLargeVideo(video[0].src); + VideoLayout.enableActiveSpeaker(endpointId, true); } } } diff --git a/desktopsharing.js b/desktopsharing.js index dc8194f54f..2e5e418957 100644 --- a/desktopsharing.js +++ b/desktopsharing.js @@ -1,4 +1,4 @@ -/* global $, config, connection, chrome, alert, getUserMediaWithConstraints, change_local_video, getConferenceHandler */ +/* global $, config, connection, chrome, alert, getUserMediaWithConstraints, changeLocalVideo, getConferenceHandler */ /** * Indicates that desktop stream is currently in use(for toggle purpose). * @type {boolean} @@ -251,7 +251,9 @@ function newStreamCreated(stream) { var oldStream = connection.jingle.localVideo; - change_local_video(stream, !isUsingScreenStream); + connection.jingle.localVideo = stream; + + VideoLayout.changeLocalVideo(stream, !isUsingScreenStream); var conferenceHandler = getConferenceHandler(); if (conferenceHandler) { diff --git a/etherpad.js b/etherpad.js index 6bb63e2e10..78111fd0cd 100644 --- a/etherpad.js +++ b/etherpad.js @@ -45,8 +45,8 @@ var Etherpad = (function (my) { if (Prezi.isPresentationVisible()) { largeVideo.css({opacity: '0'}); } else { - setLargeVideoVisible(false); - dockToolbar(true); + VideoLayout.setLargeVideoVisible(false); + Toolbar.dockToolbar(true); } $('#etherpad>iframe').fadeIn(300, function () { @@ -63,8 +63,8 @@ var Etherpad = (function (my) { document.body.style.background = 'black'; if (!isPresentation) { $('#largeVideo').fadeIn(300, function () { - setLargeVideoVisible(true); - dockToolbar(false); + VideoLayout.setLargeVideoVisible(true); + Toolbar.dockToolbar(false); }); } }); diff --git a/index.html b/index.html index 1bfbe9faba..4aa65f852b 100644 --- a/index.html +++ b/index.html @@ -35,6 +35,8 @@ + + @@ -78,7 +80,7 @@
- +
@@ -91,7 +93,7 @@
-
+
diff --git a/prezi.js b/prezi.js index 8de0688d65..e3f37376ea 100644 --- a/prezi.js +++ b/prezi.js @@ -19,10 +19,10 @@ var Prezi = (function (my) { $(document).trigger("video.selected", [true]); $('#largeVideo').fadeOut(300, function () { - setLargeVideoVisible(false); + VideoLayout.setLargeVideoVisible(false); $('#presentation>iframe').fadeIn(300, function() { $('#presentation>iframe').css({opacity:'1'}); - dockToolbar(true); + Toolbar.dockToolbar(true); }); }); } @@ -32,8 +32,8 @@ var Prezi = (function (my) { $('#presentation>iframe').css({opacity:'0'}); $('#reloadPresentation').css({display:'none'}); $('#largeVideo').fadeIn(300, function() { - setLargeVideoVisible(true); - dockToolbar(false); + VideoLayout.setLargeVideoVisible(true); + Toolbar.dockToolbar(false); }); }); } @@ -177,8 +177,8 @@ var Prezi = (function (my) { // We explicitly don't specify the peer jid here, because we don't want // this video to be dealt with as a peer related one (for example we // don't want to show a mute/kick menu for this one, etc.). - addRemoteVideoContainer(null, elementId); - resizeThumbnails(); + VideoLayout.addRemoteVideoContainer(null, elementId); + VideoLayout.resizeThumbnails(); var controlsEnabled = false; if (jid === connection.emuc.myroomjid) diff --git a/util.js b/util.js index b7c2d315cf..15abda6bb2 100644 --- a/util.js +++ b/util.js @@ -51,10 +51,28 @@ var Util = (function (my) { * Returns the available video width. */ my.getAvailableVideoWidth = function () { - var chatspaceWidth = $('#chatspace').is(":visible") ? $('#chatspace').width() : 0; + var chatspaceWidth + = $('#chatspace').is(":visible") ? $('#chatspace').width() : 0; return window.innerWidth - chatspaceWidth; }; + my.imageToGrayScale = function(canvas) { + var context = canvas.getContext('2d'); + var imgData = context.getImageData(0, 0, canvas.width, canvas.height); + var pixels = imgData.data; + + for (var i = 0, n = pixels.length; i < n; i += 4) { + var grayscale + = pixels[i] * .3 + pixels[i+1] * .59 + pixels[i+2] * .11; + pixels[i ] = grayscale; // red + pixels[i+1] = grayscale; // green + pixels[i+2] = grayscale; // blue + // pixels[i+3] is alpha + } + // redraw the image in black & white + context.putImageData(imgData, 0, 0); + }; + return my; }(Util || {}));