diff --git a/config.js b/config.js
index ba3c1495a..6051f707e 100644
--- a/config.js
+++ b/config.js
@@ -33,5 +33,8 @@ var config = {
enableSimulcast: false,
enableFirefoxSupport: true, //firefox support is still experimental and
// will work when simulcast is *disabled* and rtcpMux & bundle are *enabled*.
- logStats: false // Enable logging of PeerConnection stats via the focus
+ logStats: false, // Enable logging of PeerConnection stats via the focus
+ /*noticeMessage: 'Service update is scheduled for 16th March 2015. ' +
+ 'During that time service will not be available. ' +
+ 'Apologise for inconvenience.'*/
};
diff --git a/css/notice.css b/css/notice.css
new file mode 100644
index 000000000..6c8e0fc1b
--- /dev/null
+++ b/css/notice.css
@@ -0,0 +1,11 @@
+#notice {
+ position: relative;
+ z-index: 3;
+ margin-top: 6px;
+}
+#noticeText {
+ background-color: #000000;
+ color: white;
+ padding: 3px;
+ border-radius: 5px;
+}
\ No newline at end of file
diff --git a/index.html b/index.html
index a98a477b2..87594337c 100644
--- a/index.html
+++ b/index.html
@@ -19,7 +19,7 @@
-
+
@@ -28,6 +28,7 @@
+
@@ -120,6 +121,9 @@
");
- APP.translation.translateElement($(".connection_info"));
-};
-
-/**
- * Hides the popover
- */
-ConnectionIndicator.prototype.hide = function () {
- this.popover.forceHide();
-};
-
-/**
- * Hides the indicator
- */
-ConnectionIndicator.prototype.hideIndicator = function () {
- this.connectionIndicatorContainer.style.display = "none";
- if(this.popover)
- this.popover.forceHide();
-};
-
-module.exports = ConnectionIndicator;
-},{"../util/JitsiPopover":26}],31:[function(require,module,exports){
-var AudioLevels = require("../audio_levels/AudioLevels");
-var Avatar = require("../avatar/Avatar");
-var Chat = require("../side_pannels/chat/Chat");
-var ContactList = require("../side_pannels/contactlist/ContactList");
-var UIUtil = require("../util/UIUtil");
-var ConnectionIndicator = require("./ConnectionIndicator");
-var NicknameHandler = require("../util/NicknameHandler");
-var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
-var UIEvents = require("../../../service/UI/UIEvents");
-
-var currentDominantSpeaker = null;
-var lastNCount = config.channelLastN;
-var localLastNCount = config.channelLastN;
-var localLastNSet = [];
-var lastNEndpointsCache = [];
-var lastNPickupJid = null;
-var largeVideoState = {
- updateInProgress: false,
- newSrc: ''
-};
-
-var eventEmitter = null;
-
-/**
- * Currently focused video "src"(displayed in large video).
- * @type {String}
- */
-var focusedVideoInfo = null;
-
-/**
- * Indicates if we have muted our audio before the conference has started.
- * @type {boolean}
- */
-var preMuted = false;
-
-var mutedAudios = {};
-
-var flipXLocalVideo = true;
-var currentVideoWidth = null;
-var currentVideoHeight = null;
-
-var localVideoSrc = null;
-
-function videoactive( videoelem) {
- if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
- // ignore mixedmslabela0 and v0
-
- videoelem.show();
- VideoLayout.resizeThumbnails();
-
- var videoParent = videoelem.parent();
- var parentResourceJid = null;
- if (videoParent)
- parentResourceJid
- = VideoLayout.getPeerContainerResourceJid(videoParent[0]);
-
- // Update the large video to the last added video only if there's no
- // current dominant, focused speaker or prezi playing or update it to
- // the current dominant speaker.
- if ((!focusedVideoInfo &&
- !VideoLayout.getDominantSpeakerResourceJid() &&
- !require("../prezi/Prezi").isPresentationVisible()) ||
- (parentResourceJid &&
- VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) {
- VideoLayout.updateLargeVideo(
- APP.RTC.getVideoSrc(videoelem[0]),
- 1,
- parentResourceJid);
- }
-
- VideoLayout.showModeratorIndicator();
- }
-}
-
-function waitForRemoteVideo(selector, ssrc, stream, jid) {
- // XXX(gp) so, every call to this function is *always* preceded by a call
- // to the RTC.attachMediaStream() function but that call is *not* followed
- // by an update to the videoSrcToSsrc map!
- //
- // The above way of doing things results in video SRCs that don't correspond
- // to any SSRC for a short period of time (to be more precise, for as long
- // the waitForRemoteVideo takes to complete). This causes problems (see
- // bellow).
- //
- // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
- // a second time in here and only then update the videoSrcToSsrc map? Why
- // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
- // is called the first time? I actually do that in the lastN changed event
- // handler because the "orphan" video SRC is causing troubles there. The
- // purpose of this method would then be to fire the "videoactive.jingle".
- //
- // Food for though I guess :-)
-
- if (selector.removed || !selector.parent().is(":visible")) {
- console.warn("Media removed before had started", selector);
- return;
- }
-
- if (stream.id === 'mixedmslabel') return;
-
- if (selector[0].currentTime > 0) {
- var videoStream = APP.simulcast.getReceivingVideoStream(stream);
- APP.RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
- videoactive(selector);
- } else {
- setTimeout(function () {
- waitForRemoteVideo(selector, ssrc, stream, jid);
- }, 250);
- }
-}
-
-/**
- * Returns an array of the video horizontal and vertical indents,
- * so that if fits its parent.
- *
- * @return an array with 2 elements, the horizontal indent and the vertical
- * indent
- */
-function getCameraVideoPosition(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
- // Parent height isn't completely calculated when we position the video in
- // full screen mode and this is why we use the screen height in this case.
- // Need to think it further at some point and implement it properly.
- var isFullScreen = document.fullScreen ||
- document.mozFullScreen ||
- document.webkitIsFullScreen;
- if (isFullScreen)
- videoSpaceHeight = window.innerHeight;
-
- var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
- var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
-
- return [horizontalIndent, verticalIndent];
-}
-
-/**
- * Returns an array of the video horizontal and vertical indents.
- * Centers horizontally and top aligns vertically.
- *
- * @return an array with 2 elements, the horizontal indent and the vertical
- * indent
- */
-function getDesktopVideoPosition(videoWidth,
- videoHeight,
- videoSpaceWidth,
- videoSpaceHeight) {
-
- var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
-
- var verticalIndent = 0;// Top aligned
-
- return [horizontalIndent, verticalIndent];
-}
-
-
-/**
- * Returns an array of the video dimensions, so that it covers the screen.
- * It leaves no empty areas, but some parts of the video might not be visible.
- *
- * @return an array with 2 elements, the video width and the video height
- */
-function getCameraVideoSize(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);
-
- if (availableWidth / aspectRatio < videoSpaceHeight) {
- availableHeight = videoSpaceHeight;
- availableWidth = availableHeight * aspectRatio;
- }
-
- if (availableHeight * aspectRatio < videoSpaceWidth) {
- availableWidth = videoSpaceWidth;
- availableHeight = availableWidth / aspectRatio;
- }
-
- return [availableWidth, availableHeight];
-}
-
-/**
- * Sets the display name for the given video span id.
- */
-function setDisplayName(videoSpanId, displayName, key) {
- var nameSpan = $('#' + videoSpanId + '>span.displayname');
- var defaultLocalDisplayName = APP.translation.generateTranslatonHTML(
- interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
-
- // 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) {
- if (displayName && displayName.length > 0)
- {
- var meHTML = APP.translation.generateTranslatonHTML("me");
- $('#localDisplayName').html(displayName + ' (' + meHTML + ')');
- }
- else
- $('#localDisplayName').html(defaultLocalDisplayName);
- } else {
- if (displayName && displayName.length > 0)
- {
- $('#' + videoSpanId + '_name').html(displayName);
- }
- else if (key && key.length > 0)
- {
- var nameHtml = APP.translation.generateTranslatonHTML(key);
- $('#' + videoSpanId + '_name').html(nameHtml);
- }
- else
- $('#' + videoSpanId + '_name').text(
- interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
- }
- } else {
- var editButton = null;
-
- nameSpan = document.createElement('span');
- nameSpan.className = 'displayname';
- $('#' + videoSpanId)[0].appendChild(nameSpan);
-
- if (videoSpanId === 'localVideoContainer') {
- editButton = createEditDisplayNameButton();
- if (displayName && displayName.length > 0) {
- var meHTML = APP.translation.generateTranslatonHTML("me");
- nameSpan.innerHTML = displayName + meHTML;
- }
- else
- nameSpan.innerHTML = defaultLocalDisplayName;
- }
- else {
- if (displayName && displayName.length > 0) {
-
- nameSpan.innerText = displayName;
- }
- else
- nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
- }
-
-
- if (!editButton) {
- nameSpan.id = videoSpanId + '_name';
- } else {
- nameSpan.id = 'localDisplayName';
- $('#' + videoSpanId)[0].appendChild(editButton);
- //translates popover of edit button
- APP.translation.translateElement($("a.displayname"));
-
- var editableText = document.createElement('input');
- editableText.className = 'displayname';
- editableText.type = 'text';
- editableText.id = 'editDisplayName';
-
- if (displayName && displayName.length) {
- editableText.value
- = displayName;
- }
-
- var defaultNickname = APP.translation.translateString(
- "defaultNickname", {name: "Jane Pink"});
- editableText.setAttribute('style', 'display:none;');
- editableText.setAttribute('data-18n',
- '[placeholder]defaultNickname');
- editableText.setAttribute("data-i18n-options",
- JSON.stringify({name: "Jane Pink"}));
- editableText.setAttribute("placeholder", defaultNickname);
-
- $('#' + videoSpanId)[0].appendChild(editableText);
-
- $('#localVideoContainer .displayname')
- .bind("click", function (e) {
-
- e.preventDefault();
- e.stopPropagation();
- $('#localDisplayName').hide();
- $('#editDisplayName').show();
- $('#editDisplayName').focus();
- $('#editDisplayName').select();
-
- $('#editDisplayName').one("focusout", function (e) {
- VideoLayout.inputDisplayNameHandler(this.value);
- });
-
- $('#editDisplayName').on('keydown', function (e) {
- if (e.keyCode === 13) {
- e.preventDefault();
- VideoLayout.inputDisplayNameHandler(this.value);
- }
- });
- });
- }
- }
-}
-
-/**
- * Gets the selector of video thumbnail container for the user identified by
- * given userJid
- * @param resourceJid user's Jid for whom we want to get the video container.
- */
-function getParticipantContainer(resourceJid)
-{
- if (!resourceJid)
- return null;
-
- if (resourceJid === APP.xmpp.myResource())
- return $("#localVideoContainer");
- else
- return $("#participant_" + resourceJid);
-}
-
-/**
- * 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'});
-}
-
-/**
- * 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);
-
-//
");
+ APP.translation.translateElement($(".connection_info"));
+};
+
+/**
+ * Hides the popover
+ */
+ConnectionIndicator.prototype.hide = function () {
+ this.popover.forceHide();
+};
+
+/**
+ * Hides the indicator
+ */
+ConnectionIndicator.prototype.hideIndicator = function () {
+ this.connectionIndicatorContainer.style.display = "none";
+ if(this.popover)
+ this.popover.forceHide();
+};
+
+module.exports = ConnectionIndicator;
+},{"../util/JitsiPopover":27}],32:[function(require,module,exports){
+var AudioLevels = require("../audio_levels/AudioLevels");
+var Avatar = require("../avatar/Avatar");
+var Chat = require("../side_pannels/chat/Chat");
+var ContactList = require("../side_pannels/contactlist/ContactList");
+var UIUtil = require("../util/UIUtil");
+var ConnectionIndicator = require("./ConnectionIndicator");
+var NicknameHandler = require("../util/NicknameHandler");
+var MediaStreamType = require("../../../service/RTC/MediaStreamTypes");
+var UIEvents = require("../../../service/UI/UIEvents");
+
+var currentDominantSpeaker = null;
+var lastNCount = config.channelLastN;
+var localLastNCount = config.channelLastN;
+var localLastNSet = [];
+var lastNEndpointsCache = [];
+var lastNPickupJid = null;
+var largeVideoState = {
+ updateInProgress: false,
+ newSrc: ''
+};
+
+var eventEmitter = null;
+
+/**
+ * Currently focused video "src"(displayed in large video).
+ * @type {String}
+ */
+var focusedVideoInfo = null;
+
+/**
+ * Indicates if we have muted our audio before the conference has started.
+ * @type {boolean}
+ */
+var preMuted = false;
+
+var mutedAudios = {};
+
+var flipXLocalVideo = true;
+var currentVideoWidth = null;
+var currentVideoHeight = null;
+
+var localVideoSrc = null;
+
+function videoactive( videoelem) {
+ if (videoelem.attr('id').indexOf('mixedmslabel') === -1) {
+ // ignore mixedmslabela0 and v0
+
+ videoelem.show();
+ VideoLayout.resizeThumbnails();
+
+ var videoParent = videoelem.parent();
+ var parentResourceJid = null;
+ if (videoParent)
+ parentResourceJid
+ = VideoLayout.getPeerContainerResourceJid(videoParent[0]);
+
+ // Update the large video to the last added video only if there's no
+ // current dominant, focused speaker or prezi playing or update it to
+ // the current dominant speaker.
+ if ((!focusedVideoInfo &&
+ !VideoLayout.getDominantSpeakerResourceJid() &&
+ !require("../prezi/Prezi").isPresentationVisible()) ||
+ (parentResourceJid &&
+ VideoLayout.getDominantSpeakerResourceJid() === parentResourceJid)) {
+ VideoLayout.updateLargeVideo(
+ APP.RTC.getVideoSrc(videoelem[0]),
+ 1,
+ parentResourceJid);
+ }
+
+ VideoLayout.showModeratorIndicator();
+ }
+}
+
+function waitForRemoteVideo(selector, ssrc, stream, jid) {
+ // XXX(gp) so, every call to this function is *always* preceded by a call
+ // to the RTC.attachMediaStream() function but that call is *not* followed
+ // by an update to the videoSrcToSsrc map!
+ //
+ // The above way of doing things results in video SRCs that don't correspond
+ // to any SSRC for a short period of time (to be more precise, for as long
+ // the waitForRemoteVideo takes to complete). This causes problems (see
+ // bellow).
+ //
+ // I'm wondering why we need to do that; i.e. why call RTC.attachMediaStream()
+ // a second time in here and only then update the videoSrcToSsrc map? Why
+ // not simply update the videoSrcToSsrc map when the RTC.attachMediaStream()
+ // is called the first time? I actually do that in the lastN changed event
+ // handler because the "orphan" video SRC is causing troubles there. The
+ // purpose of this method would then be to fire the "videoactive.jingle".
+ //
+ // Food for though I guess :-)
+
+ if (selector.removed || !selector.parent().is(":visible")) {
+ console.warn("Media removed before had started", selector);
+ return;
+ }
+
+ if (stream.id === 'mixedmslabel') return;
+
+ if (selector[0].currentTime > 0) {
+ var videoStream = APP.simulcast.getReceivingVideoStream(stream);
+ APP.RTC.attachMediaStream(selector, videoStream); // FIXME: why do i have to do this for FF?
+ videoactive(selector);
+ } else {
+ setTimeout(function () {
+ waitForRemoteVideo(selector, ssrc, stream, jid);
+ }, 250);
+ }
+}
+
+/**
+ * Returns an array of the video horizontal and vertical indents,
+ * so that if fits its parent.
+ *
+ * @return an array with 2 elements, the horizontal indent and the vertical
+ * indent
+ */
+function getCameraVideoPosition(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+ // Parent height isn't completely calculated when we position the video in
+ // full screen mode and this is why we use the screen height in this case.
+ // Need to think it further at some point and implement it properly.
+ var isFullScreen = document.fullScreen ||
+ document.mozFullScreen ||
+ document.webkitIsFullScreen;
+ if (isFullScreen)
+ videoSpaceHeight = window.innerHeight;
+
+ var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
+ var verticalIndent = (videoSpaceHeight - videoHeight) / 2;
+
+ return [horizontalIndent, verticalIndent];
+}
+
+/**
+ * Returns an array of the video horizontal and vertical indents.
+ * Centers horizontally and top aligns vertically.
+ *
+ * @return an array with 2 elements, the horizontal indent and the vertical
+ * indent
+ */
+function getDesktopVideoPosition(videoWidth,
+ videoHeight,
+ videoSpaceWidth,
+ videoSpaceHeight) {
+
+ var horizontalIndent = (videoSpaceWidth - videoWidth) / 2;
+
+ var verticalIndent = 0;// Top aligned
+
+ return [horizontalIndent, verticalIndent];
+}
+
+
+/**
+ * Returns an array of the video dimensions, so that it covers the screen.
+ * It leaves no empty areas, but some parts of the video might not be visible.
+ *
+ * @return an array with 2 elements, the video width and the video height
+ */
+function getCameraVideoSize(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);
+
+ if (availableWidth / aspectRatio < videoSpaceHeight) {
+ availableHeight = videoSpaceHeight;
+ availableWidth = availableHeight * aspectRatio;
+ }
+
+ if (availableHeight * aspectRatio < videoSpaceWidth) {
+ availableWidth = videoSpaceWidth;
+ availableHeight = availableWidth / aspectRatio;
+ }
+
+ return [availableWidth, availableHeight];
+}
+
+/**
+ * Sets the display name for the given video span id.
+ */
+function setDisplayName(videoSpanId, displayName, key) {
+ var nameSpan = $('#' + videoSpanId + '>span.displayname');
+ var defaultLocalDisplayName = APP.translation.generateTranslatonHTML(
+ interfaceConfig.DEFAULT_LOCAL_DISPLAY_NAME);
+
+ // 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) {
+ if (displayName && displayName.length > 0)
+ {
+ var meHTML = APP.translation.generateTranslatonHTML("me");
+ $('#localDisplayName').html(displayName + ' (' + meHTML + ')');
+ }
+ else
+ $('#localDisplayName').html(defaultLocalDisplayName);
+ } else {
+ if (displayName && displayName.length > 0)
+ {
+ $('#' + videoSpanId + '_name').html(displayName);
+ }
+ else if (key && key.length > 0)
+ {
+ var nameHtml = APP.translation.generateTranslatonHTML(key);
+ $('#' + videoSpanId + '_name').html(nameHtml);
+ }
+ else
+ $('#' + videoSpanId + '_name').text(
+ interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME);
+ }
+ } else {
+ var editButton = null;
+
+ nameSpan = document.createElement('span');
+ nameSpan.className = 'displayname';
+ $('#' + videoSpanId)[0].appendChild(nameSpan);
+
+ if (videoSpanId === 'localVideoContainer') {
+ editButton = createEditDisplayNameButton();
+ if (displayName && displayName.length > 0) {
+ var meHTML = APP.translation.generateTranslatonHTML("me");
+ nameSpan.innerHTML = displayName + meHTML;
+ }
+ else
+ nameSpan.innerHTML = defaultLocalDisplayName;
+ }
+ else {
+ if (displayName && displayName.length > 0) {
+
+ nameSpan.innerText = displayName;
+ }
+ else
+ nameSpan.innerText = interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME;
+ }
+
+
+ if (!editButton) {
+ nameSpan.id = videoSpanId + '_name';
+ } else {
+ nameSpan.id = 'localDisplayName';
+ $('#' + videoSpanId)[0].appendChild(editButton);
+ //translates popover of edit button
+ APP.translation.translateElement($("a.displayname"));
+
+ var editableText = document.createElement('input');
+ editableText.className = 'displayname';
+ editableText.type = 'text';
+ editableText.id = 'editDisplayName';
+
+ if (displayName && displayName.length) {
+ editableText.value
+ = displayName;
+ }
+
+ var defaultNickname = APP.translation.translateString(
+ "defaultNickname", {name: "Jane Pink"});
+ editableText.setAttribute('style', 'display:none;');
+ editableText.setAttribute('data-18n',
+ '[placeholder]defaultNickname');
+ editableText.setAttribute("data-i18n-options",
+ JSON.stringify({name: "Jane Pink"}));
+ editableText.setAttribute("placeholder", defaultNickname);
+
+ $('#' + videoSpanId)[0].appendChild(editableText);
+
+ $('#localVideoContainer .displayname')
+ .bind("click", function (e) {
+
+ e.preventDefault();
+ e.stopPropagation();
+ $('#localDisplayName').hide();
+ $('#editDisplayName').show();
+ $('#editDisplayName').focus();
+ $('#editDisplayName').select();
+
+ $('#editDisplayName').one("focusout", function (e) {
+ VideoLayout.inputDisplayNameHandler(this.value);
+ });
+
+ $('#editDisplayName').on('keydown', function (e) {
+ if (e.keyCode === 13) {
+ e.preventDefault();
+ VideoLayout.inputDisplayNameHandler(this.value);
+ }
+ });
+ });
+ }
+ }
+}
+
+/**
+ * Gets the selector of video thumbnail container for the user identified by
+ * given userJid
+ * @param resourceJid user's Jid for whom we want to get the video container.
+ */
+function getParticipantContainer(resourceJid)
+{
+ if (!resourceJid)
+ return null;
+
+ if (resourceJid === APP.xmpp.myResource())
+ return $("#localVideoContainer");
+ else
+ return $("#participant_" + resourceJid);
+}
+
+/**
+ * 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'});
+}
+
+/**
+ * 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);
+
+//