diff --git a/toolbar.js b/toolbar.js
new file mode 100644
index 000000000..b932286f5
--- /dev/null
+++ b/toolbar.js
@@ -0,0 +1,232 @@
+var Toolbar = (function (my) {
+ var INITIAL_TOOLBAR_TIMEOUT = 20000;
+ var TOOLBAR_TIMEOUT = INITIAL_TOOLBAR_TIMEOUT;
+
+ /**
+ * Opens the lock room dialog.
+ */
+ my.openLockDialog = function() {
+ // 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.
+ */
+ my.openLinkDialog = function() {
+ $.prompt('',
+ {
+ title: "Share this link with everyone you want to invite",
+ persistent: false,
+ buttons: { "Cancel": false},
+ loaded: function (event) {
+ document.getElementById('inviteLinkRef').select();
+ }
+ }
+ );
+ };
+
+ /**
+ * Opens the settings dialog.
+ */
+ my.openSettingsDialog = function() {
+ $.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);
+ }
+ */
+ }
+ }
+ }
+ );
+ };
+
+ /**
+ * Toggles the application in and out of full screen mode
+ * (a.k.a. presentation mode in Chrome).
+ */
+ my.toggleFullScreen = function() {
+ 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 main toolbar.
+ */
+ my.showToolbar = function() {
+ if (!$('#header').is(':visible')) {
+ $('#header').show("slide", { direction: "up", duration: 300});
+
+ if (toolbarTimeout) {
+ clearTimeout(toolbarTimeout);
+ toolbarTimeout = null;
+ }
+ toolbarTimeout = setTimeout(hideToolbar, TOOLBAR_TIMEOUT);
+ TOOLBAR_TIMEOUT = 4000;
+ }
+
+ 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
+ */
+ my.dockToolbar = function(isDock) {
+ if (isDock) {
+ // First make sure the toolbar is shown.
+ if (!$('#header').is(':visible')) {
+ Toolbar.showToolbar();
+ }
+ // Then clear the time out, to dock the toolbar.
+ clearTimeout(toolbarTimeout);
+ toolbarTimeout = null;
+ }
+ else {
+ if (!$('#header').is(':visible')) {
+ Toolbar.showToolbar();
+ }
+ else {
+ toolbarTimeout = setTimeout(hideToolbar, TOOLBAR_TIMEOUT);
+ }
+ }
+ };
+
+ /**
+ * Updates the lock button state.
+ */
+ my.updateLockButton = function() {
+ 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, TOOLBAR_TIMEOUT);
+ }
+ };
+
+ return my;
+}(Toolbar || {}));
\ No newline at end of file
diff --git a/videolayout.js b/videolayout.js
new file mode 100644
index 000000000..5024acfe9
--- /dev/null
+++ b/videolayout.js
@@ -0,0 +1,859 @@
+var VideoLayout = (function (my) {
+ var preMuted = false;
+ var currentActiveSpeaker = null;
+
+ my.changeLocalAudio = function(stream) {
+ connection.jingle.localAudio = stream;
+
+ RTC.attachMediaStream($('#localAudio'), stream);
+ document.getElementById('localAudio').autoplay = true;
+ document.getElementById('localAudio').volume = 0;
+ if (preMuted) {
+ toggleAudio();
+ preMuted = false;
+ }
+ };
+
+ my.changeLocalVideo = function(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 () {
+ VideoLayout.handleVideoThumbClicked(localVideo.src);
+ });
+ // Add hover handler
+ $('#localVideoContainer').hover(
+ function() {
+ VideoLayout.showDisplayName('localVideoContainer', true);
+ },
+ function() {
+ if (focusedVideoSrc !== localVideo.src)
+ VideoLayout.showDisplayName('localVideoContainer', false);
+ }
+ );
+ // Add stream ended handler
+ stream.onended = function () {
+ localVideoContainer.removeChild(localVideo);
+ VideoLayout.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;
+ VideoLayout.updateLargeVideo(localVideoSrc, 0);
+ };
+
+ /**
+ * Checks if removed video is currently displayed and tries to display another one instead.
+ * @param removedVideoSrc src stream identifier of the video.
+ */
+ my.checkChangeLargeVideo = function(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) {
+ VideoLayout.updateLargeVideo(pick.src, pick.volume);
+ } else {
+ console.warn("Failed to elect large video");
+ }
+ }
+ };
+
+
+ /**
+ * Updates the large video with the given new video source.
+ */
+ my.updateLargeVideo = function(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);
+ });
+ }
+ };
+
+ my.handleVideoThumbClicked = function(videoSrc) {
+ // Restore style for previously focused video
+ var focusJid = getJidFromVideoSrc(focusedVideoSrc);
+ var oldContainer =
+ getParticipantContainer(focusJid);
+
+ if (oldContainer) {
+ oldContainer.removeClass("videoContainerFocused");
+ VideoLayout.enableActiveSpeaker(
+ Strophe.getResourceFromJid(focusJid), false);
+ }
+
+ // 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");
+
+ var resourceJid = Strophe.getResourceFromJid(userJid);
+ VideoLayout.enableActiveSpeaker(resourceJid, true);
+ }
+
+ $(document).trigger("video.selected", [false]);
+
+ VideoLayout.updateLargeVideo(videoSrc, 1);
+
+ $('audio').each(function (idx, el) {
+ if (el.id.indexOf('mixedmslabel') !== -1) {
+ el.volume = 0;
+ el.volume = 1;
+ }
+ });
+ };
+
+ /**
+ * Positions the large video.
+ *
+ * @param videoWidth the stream video width
+ * @param videoHeight the stream video height
+ */
+ my.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);
+ };
+
+ /**
+ * Shows/hides the large video.
+ */
+ my.setLargeVideoVisible = function(isVisible) {
+ if (isVisible) {
+ $('#largeVideo').css({visibility: 'visible'});
+ $('.watermark').css({visibility: 'visible'});
+ }
+ else {
+ $('#largeVideo').css({visibility: 'hidden'});
+ $('.watermark').css({visibility: 'hidden'});
+ }
+ };
+
+
+ /**
+ * Checks if container for participant identified by given peerJid exists
+ * in the document and creates it eventually.
+ *
+ * @param peerJid peer Jid to check.
+ */
+ my.ensurePeerContainerExists = function(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
+ = VideoLayout.addRemoteVideoContainer(peerJid, videoSpanId);
+
+ var nickfield = document.createElement('span');
+ nickfield.className = "nick";
+ nickfield.appendChild(document.createTextNode(peerResource));
+ container.appendChild(nickfield);
+ VideoLayout.resizeThumbnails();
+ };
+
+ my.addRemoteVideoContainer = function(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;
+ };
+
+ /**
+ * Shows the display name for the given video.
+ */
+ my.setDisplayName = function(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")) {
+ if (nickname) {
+ $('#localDisplayName').text(nickname + " (me)");
+ $('#localDisplayName').show();
+ }
+ else {
+ $('#localDisplayName').text(nickname);
+ }
+
+ $('#editDisplayName').hide();
+ }
+ };
+
+ $('#editDisplayName').one("focusout", function (e) {
+ inputDisplayNameHandler(this.value);
+ });
+
+ $('#editDisplayName').on('keydown', function (e) {
+ if (e.keyCode === 13) {
+ e.preventDefault();
+ inputDisplayNameHandler(this.value);
+ }
+ });
+ });
+ }
+ }
+ };
+
+ /**
+ * Shows/hides the display name on the remote video.
+ * @param videoSpanId the identifier of the video span element
+ * @param isShow indicates if the display name should be shown or hidden
+ */
+ my.showDisplayName = function(videoSpanId, isShow) {
+ var nameSpan = $('#' + videoSpanId + '>span.displayname').get(0);
+
+ if (isShow) {
+ if (nameSpan && nameSpan.innerHTML && nameSpan.innerHTML.length)
+ nameSpan.setAttribute("style", "display:inline-block;");
+ }
+ else {
+ if (nameSpan)
+ nameSpan.setAttribute("style", "display:none;");
+ }
+ };
+
+ /**
+ * 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.
+ */
+ my.showFocusIndicator = function() {
+ 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);
+ }
+ }
+ };
+
+ /**
+ * Shows video muted indicator over small videos.
+ */
+ my.showVideoIndicator = function(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);
+ }
+ };
+
+ /**
+ * Shows audio muted indicator over small videos.
+ */
+ my.showAudioIndicator = function(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);
+ }
+ };
+
+ /**
+ * Resizes the large video container.
+ */
+ my.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);
+
+ VideoLayout.resizeThumbnails();
+ };
+
+ /**
+ * Resizes thumbnails.
+ */
+ my.resizeThumbnails = function() {
+ 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);
+ };
+
+ /**
+ * Enables the active speaker UI.
+ *
+ * @param resourceJid the jid indicating the video element to
+ * activate/deactivate
+ * @param isEnable indicates if the active speaker should be enabled or
+ * disabled
+ */
+ my.enableActiveSpeaker = function(resourceJid, isEnable) {
+ var videoSpanId = null;
+ if (resourceJid
+ === Strophe.getResourceFromJid(connection.emuc.myroomjid))
+ videoSpanId = 'localVideoWrapper';
+ else
+ videoSpanId = 'participant_' + resourceJid;
+
+ videoSpan = document.getElementById(videoSpanId);
+
+ if (!videoSpan) {
+ console.error("No video element for jid", resourceJid);
+ return;
+ }
+
+ // If there's an active speaker (automatically) selected we have to
+ // disable this state and update the current active speaker.
+ if (isEnable) {
+ if (currentActiveSpeaker)
+ VideoLayout.enableActiveSpeaker(currentActiveSpeaker, false);
+ else
+ currentActiveSpeaker = resourceJid;
+ }
+ else if (resourceJid === currentActiveSpeaker)
+ currentActiveSpeaker = null;
+
+ var activeSpeakerCanvas = $('#' + videoSpanId + '>canvas');
+ var videoElement = $('#' + videoSpanId + '>video');
+ var canvasSize = calculateThumbnailSize();
+
+ if (isEnable && (!activeSpeakerCanvas
+ || activeSpeakerCanvas.length === 0)) {
+
+ activeSpeakerCanvas = document.createElement('canvas');
+ activeSpeakerCanvas.width = canvasSize[0];
+ activeSpeakerCanvas.height = canvasSize[1];
+
+ // We flip the canvas image if this is the local video.
+ if (videoSpanId === 'localVideoWrapper')
+ activeSpeakerCanvas.className += " flipVideoX";
+
+ videoSpan.appendChild(activeSpeakerCanvas);
+ activeSpeakerCanvas.addEventListener(
+ 'click',
+ function() {
+ VideoLayout.handleVideoThumbClicked(
+ videoElement.get(0).src);
+ }, false);
+ }
+ else {
+ activeSpeakerCanvas = activeSpeakerCanvas.get(0);
+ }
+
+ if (videoElement && videoElement.length > 0) {
+ var video = document.getElementById(videoElement.get(0).id);
+ if (isEnable) {
+ var context = activeSpeakerCanvas.getContext('2d');
+
+ context.fillRect(0, 0, canvasSize[0], canvasSize[1]);
+ context.drawImage(video, 0, 0, canvasSize[0], canvasSize[1]);
+ Util.imageToGrayScale(activeSpeakerCanvas);
+
+ VideoLayout
+ .showDisplayName(videoSpanId, true);
+ activeSpeakerCanvas
+ .setAttribute('style', 'display:block !important;');
+ video.setAttribute('style', 'display:none !important;');
+ }
+ else {
+ VideoLayout
+ .showDisplayName(videoSpanId, false);
+ video.setAttribute('style', 'display:block !important;');
+ activeSpeakerCanvas
+ .setAttribute('style', 'display:none !important;');
+ }
+ }
+ };
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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'});
+ }
+
+ /**
+ * Calculates the thumbnail size.
+ */
+ 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];
+ };
+
+ /**
+ * 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];
+ }
+
+ /**
+ * Creates the edit display name button.
+ *
+ * @returns the edit button
+ */
+ function createEditDisplayNameButton() {
+ var editButton = document.createElement('a');
+ editButton.className = 'displayname';
+ editButton.innerHTML = '';
+
+ return editButton;
+ }
+
+ /**
+ * Creates the element indicating the focus of the conference.
+ *
+ * @param parentElement the parent element where the focus indicator will
+ * be added
+ */
+ function createFocusIndicatorElement(parentElement) {
+ var focusIndicator = document.createElement('i');
+ focusIndicator.className = 'fa fa-star';
+ focusIndicator.title = "The owner of this conference";
+ parentElement.appendChild(focusIndicator);
+ }
+
+ /**
+ * Updates the remote video menu.
+ *
+ * @param jid the jid indicating the video for which we're adding a menu.
+ * @param isMuted indicates the current mute state
+ */
+ my.updateRemoteVideoMenu = function(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';
+ }
+ }
+ };
+
+ /**
+ * Adds the remote video menu element for the given jid in the
+ * given parentElement.
+ *
+ * @param jid the jid indicating the video for which we're adding a menu.
+ * @param parentElement the parent element where this menu will be added
+ */
+ 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);
+ }
+
+ $(document).bind('audiomuted.muc', function (event, jid, isMuted) {
+ var videoSpanId = null;
+ if (jid === connection.emuc.myroomjid) {
+ videoSpanId = 'localVideoContainer';
+ } else {
+ VideoLayout.ensurePeerContainerExists(jid);
+ videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
+ }
+
+ if (focus) {
+ mutedAudios[jid] = isMuted;
+ VideoLayout.updateRemoteVideoMenu(jid, isMuted);
+ }
+
+ if (videoSpanId)
+ VideoLayout.showAudioIndicator(videoSpanId, isMuted);
+ });
+
+ $(document).bind('videomuted.muc', function (event, jid, isMuted) {
+ var videoSpanId = null;
+ if (jid === connection.emuc.myroomjid) {
+ videoSpanId = 'localVideoContainer';
+ } else {
+ VideoLayout.ensurePeerContainerExists(jid);
+ videoSpanId = 'participant_' + Strophe.getResourceFromJid(jid);
+ }
+
+ if (videoSpanId)
+ VideoLayout.showVideoIndicator(videoSpanId, isMuted);
+ });
+
+ return my;
+}(VideoLayout || {}));
\ No newline at end of file